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

Compare commits

..

1 Commits

Author SHA1 Message Date
Federico Maccaroni
ca95ada8e8 MAUI Sample iOS Extension Tap gesture workaround 2023-12-26 18:47:59 -03:00
540 changed files with 9074 additions and 15071 deletions

25
.github/CODEOWNERS vendored
View File

@@ -1,21 +1,12 @@
# Please sort into logical groups with comment headers. Sort groups in order of specificity. # Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
# For example, default owners should always be the first group.
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
# #
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Default file owners # The following owners will be the default owners for everything in the repo.
* @bitwarden/dept-development-mobile # Unless a later match takes precedence
# @bitwarden/tech-leads
# DevOps for Actions and other workflow changes @bitwarden/dept-development-mobile
.github/workflows @bitwarden/dept-devops
# DevOps for Version Bumping
src/App/Platforms/Android/AndroidManifest.xml
src/iOS.Autofill/Info.plist
src/iOS.Extension/Info.plist
src/iOS.ShareExtension/Info.plist
src/App/Platforms/iOS/Info.plist
## Auth team files ## ## Auth team files ##
@@ -30,14 +21,14 @@ src/watchOS @bitwarden/team-vault-dev
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
## Crowdin Sync files ## ## Crowdin Sync files ##
src/Core/Resources/Localization @bitwarden/team-tools-dev src/App/Resources @bitwarden/team-tools-dev
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
store/apple @bitwarden/team-tools-dev store/apple @bitwarden/team-tools-dev
store/google @bitwarden/team-tools-dev store/google @bitwarden/team-tools-dev
## Locales ## ## Locales ##
src/Core/Resources/Localization/AppResources.Designer.cs src/App/Resources/AppResources.Designer.cs
src/Core/Resources/Localization/AppResources.resx src/App/Resources/AppResources.resx
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
store/apple/en store/apple/en
store/google/en store/google/en

17
.github/renovate.json vendored
View File

@@ -2,21 +2,22 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"config:base", "config:base",
"github>bitwarden/renovate-config:pin-actions",
":combinePatchMinorReleases", ":combinePatchMinorReleases",
":dependencyDashboard", ":dependencyDashboard",
":maintainLockFilesWeekly", ":maintainLockFilesWeekly",
":pinAllExceptPeerDependencies", ":pinAllExceptPeerDependencies",
":prConcurrentLimit10", ":prConcurrentLimit10",
":rebaseStalePrs", ":rebaseStalePrs",
":separateMajorReleases", "schedule:weekends",
"group:monorepos", ":separateMajorReleases"
"schedule:weekends"
], ],
"enabledManagers": ["github-actions", "npm", "nuget"], "enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
"commitMessagePrefix": "[deps]:",
"commitMessageTopic": "{{depName}}",
"packageRules": [ "packageRules": [
{
"groupName": "cargo minor",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["minor", "patch"]
},
{ {
"groupName": "gh minor", "groupName": "gh minor",
"matchManagers": ["github-actions"], "matchManagers": ["github-actions"],
@@ -31,6 +32,6 @@
"groupName": "nuget minor", "groupName": "nuget minor",
"matchManagers": ["nuget"], "matchManagers": ["nuget"],
"matchUpdateTypes": ["minor", "patch"] "matchUpdateTypes": ["minor", "patch"]
} },
] ]
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<EFBFBD>
 K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI֐߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>' F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74><EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>֋
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>•a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E>͹<EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>

Binary file not shown.

BIN
.github/secrets/play_creds.json.gpg vendored Normal file

Binary file not shown.

Binary file not shown.

29
.github/workflows/_cut_rc.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Cut RC Branch
on:
workflow_call:
jobs:
cut-rc:
name: Cut RC branch
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Check if RC branch exists
run: |
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
echo "Remote RC branch exists."
echo "Please delete current RC branch before running again."
exit 1
fi
- name: Cut RC branch
run: |
git switch --quiet --create rc
git push --quiet --set-upstream origin rc

View File

@@ -1,5 +0,0 @@
---
name: Build Beta
on:
workflow_dispatch:

View File

@@ -31,7 +31,6 @@ jobs:
- name: Print lines of code - name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup: setup:
name: Setup name: Setup
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -59,7 +58,6 @@ jobs:
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi fi
android: android:
name: Android name: Android
runs-on: windows-2022 runs-on: windows-2022
@@ -69,8 +67,7 @@ jobs:
matrix: matrix:
variant: ["prod", "qa"] variant: ["prod", "qa"]
env: env:
android_folder_path: src\App\Platforms\Android android_folder_path: src/App/Platforms/Android
android_folder_path_bash: src/App/Platforms/Android
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0 uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
@@ -78,12 +75,12 @@ jobs:
nuget-version: 6.4.0 nuget-version: 6.4.0
- name: Set up .NET - name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3 uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
# This step might be obsolete at some point as .NET MAUI workloads # This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents. # are starting to come pre-installed on the GH Actions build agents.
@@ -96,8 +93,7 @@ jobs:
- name: Install Microsoft OpenJDK 11 - name: Install Microsoft OpenJDK 11
run: | run: |
choco install microsoft-openjdk11 --no-progress choco install microsoft-openjdk11 --no-progress
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | ` Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Output "Java Home: $env:JAVA_HOME" Write-Output "Java Home: $env:JAVA_HOME"
- name: Print environment - name: Print environment
@@ -113,43 +109,39 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Login to Azure - CI Subscription - name: Decrypt secrets
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
env: env:
ACCOUNT_NAME: bitwardenci DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
CONTAINER_NAME: mobile
run: | run: |
mkdir -p $HOME/secrets mkdir -p ~/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--name app_play-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_play-keystore.jks --output none --output ./${{ env.main_app_folder_path }}/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--name app_upload-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_upload-keystore.jks --output none --output ./${{ env.main_app_folder_path }}/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--name play_creds.json --file $HOME/secrets/play_creds.json --output none --output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash shell: bash
- name: Download secrets - Google Services - name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }} if: ${{ matrix.variant == 'prod' }}
env: env:
ACCOUNT_NAME: bitwardenci DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
CONTAINER_NAME: mobile
run: | run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--name google-services.json --file ./${{ env.android_folder_path_bash }}/google-services.json --output none --output ./${{ env.android_folder_path }}/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER)) 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\"/" \ sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_folder_path_bash }}/AndroidManifest.xml ./${{ env.android_folder_path }}/AndroidManifest.xml
shell: bash shell: bash
- name: Restore packages - name: Restore packages
@@ -158,13 +150,14 @@ jobs:
- name: Restore tools - name: Restore tools
run: dotnet tool restore run: dotnet tool restore
# - name: Run Core tests # - name: Verify Format
# run: | # run: dotnet tool run dotnet-format --check
# dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" `
# /p:CustomConstants=UT
# - name: Report test results # - name: Run Core tests
# uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0 # run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
#- name: Report test results
# uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
# if: always() # if: always()
# with: # with:
# name: Test Results # name: Test Results
@@ -174,59 +167,66 @@ jobs:
- name: Build Play Store publisher - name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }} if: ${{ matrix.variant == 'prod' }}
run: dotnet build .\store\google\Publisher\Publisher.csproj /p:Configuration=Release run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Setup Android build (${{ matrix.variant }}) - name: Setup Android build (${{ matrix.variant }})
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }} run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build & Sign Android - name: Build Android
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android
- name: Sign Android Build
env: env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }} PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: | run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)/${{ env.main_app_project_path }}"; $projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden"; $packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod") if ("${{ matrix.variant }}" -ne "prod")
{ {
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}"; $packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
} }
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration" Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
$signingUploadKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_upload-keystore.jks" dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidPackageFormats=aab /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_upload-keystore.jks") /p:AndroidSigningKeyAlias=upload /p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidPackageFormats=aab `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingUploadKeyStore `
/p:AndroidSigningKeyAlias=upload `
/p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root" Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.aab"; $signedAabPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.aab");
$signedAabDestPath = "$($env:GITHUB_WORKSPACE)\$($packageName).aab"; $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
Copy-Item $signedAabPath $signedAabDestPath Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration" Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
$signingPlayKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_play-keystore.jks" dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_play-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root" Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).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)\$($packageName).apk";
Copy-Item $signedApkPath $signedApkDestPath Copy-Item $signedApkPath $signedApkDestPath
- name: Upload Prod .aab artifact - name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }} if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: com.x8bit.bitwarden.aab name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab
@@ -234,7 +234,7 @@ jobs:
- name: Upload Prod .apk artifact - name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }} if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: com.x8bit.bitwarden.apk name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk
@@ -242,7 +242,7 @@ jobs:
- name: Upload Other .apk artifact - name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }} if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
@@ -262,7 +262,7 @@ jobs:
- name: Upload .apk sha file for prod - name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }} if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: bw-android-apk-sha256.txt name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt path: ./bw-android-apk-sha256.txt
@@ -270,7 +270,7 @@ jobs:
- name: Upload .apk sha file for other - name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }} if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
@@ -283,20 +283,20 @@ jobs:
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }} || github.ref == 'refs/heads/hotfix-rc' ) }}
run: | run: |
$publisherPath = "$($env:GITHUB_WORKSPACE)\store\google\Publisher\bin\Release\net8.0\Publisher.dll" PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/net7.0/Publisher.dll"
$credsPath = "$($HOME)\secrets\play_creds.json" CREDS_PATH="$HOME/secrets/play_creds.json"
$aabPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden.aab" AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
$track = "internal" TRACK="internal"
dotnet $publisherPath $credsPath $aabPath $track dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
shell: bash
f-droid: f-droid:
name: F-Droid Build name: F-Droid Build
runs-on: windows-2022 runs-on: windows-2022
env: env:
android_folder_path: src\App\Platforms\Android android_folder_path: src/App/Platforms/Android
android_folder_path_bash: src/App/Platforms/Android
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
steps: steps:
- name: Setup NuGet - name: Setup NuGet
@@ -310,7 +310,7 @@ jobs:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3 uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
# This step might be obsolete at some point as .NET MAUI workloads # This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents. # are starting to come pre-installed on the GH Actions build agents.
@@ -337,25 +337,23 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription - name: Decrypt secrets
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
env: env:
ACCOUNT_NAME: bitwardenci DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
CONTAINER_NAME: mobile
FILE: app_fdroid-keystore.jks
run: | run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \ mkdir -p ~/secrets
--file ${{ env.android_folder_path_bash }}/$FILE --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER)) BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "##### Setting F-Droid 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\"/" \ sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_manifest_path }} ./${{ env.android_manifest_path }}
@@ -363,16 +361,28 @@ jobs:
- name: Clean for F-Droid - name: Clean for F-Droid
run: | 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 }}"); $androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
Write-Output "##### Back up project files" # Write-Output "########################################"
# Write-Output "##### Clean Android and App"
# Write-Output "########################################"
# msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
# msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original"); Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $directoryBuildProps $($directoryBuildProps + ".original"); Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest" Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML; $xml=New-Object XML;
$xml.Load($androidManifest); $xml.Load($androidManifest);
@@ -382,39 +392,80 @@ jobs:
$xml.Save($androidManifest); $xml.Save($androidManifest);
Write-Output "##### Enabling FDROID constant" # Write-Output "########################################"
# Write-Output "##### Uninstall from App.csproj"
# Write-Output "########################################"
(Get-Content $directoryBuildProps).Replace('<!-- <CustomConstants>FDROID</CustomConstants> -->', '<CustomConstants>FDROID</CustomConstants>') | Set-Content $directoryBuildProps # $xml=New-Object XML;
# $xml.Load($appPath);
# $ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
# $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
# $firebaseNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
# $firebaseNode.ParentNode.RemoveChild($firebaseNode);
# $daggerNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
# $daggerNode.ParentNode.RemoveChild($daggerNode);
# $safetyNetNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
# $safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
# $xml.Save($appPath);
# Write-Output "########################################"
# Write-Output "##### Uninstall from Core.csproj"
# Write-Output "########################################"
# $xml=New-Object XML;
# $xml.Load($corePath);
# $appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
# $appCenterNode.ParentNode.RemoveChild($appCenterNode);
# $xml.Save($corePath);
- name: Restore packages - name: Restore packages
run: dotnet restore run: dotnet restore
- name: Build & Sign F-Droid - name: Build for F-Droid
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration FDROID
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android /p:CustomConstants="FDROID"
- name: Sign for F-Droid
env: env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }} FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
run: | run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_project_path }}"; $projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden"; $packageName = "com.x8bit.bitwarden";
Write-Output "########################################"
Write-Output "##### Sign FDroid" Write-Output "##### Sign FDroid"
Write-Output "########################################"
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks" dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:CustomConstants="FDROID" --no-restore
dotnet build $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
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root" Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$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"; $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath Copy-Item $signedApkPath $signedApkDestPath
- name: Upload F-Droid .apk artifact - name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk path: ./com.x8bit.bitwarden-fdroid.apk
@@ -426,13 +477,12 @@ jobs:
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt -t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file - name: Upload F-Droid sha file
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: bw-fdroid-apk-sha256.txt name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt path: ./bw-fdroid-apk-sha256.txt
if-no-files-found: error if-no-files-found: error
ios: ios:
name: Apple iOS name: Apple iOS
runs-on: macos-13 runs-on: macos-13
@@ -445,7 +495,7 @@ jobs:
- name: Set XCode version - name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with: with:
xcode-version: 15.1 xcode-version: 15.0.1
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0 uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
@@ -475,7 +525,7 @@ jobs:
submodules: 'true' submodules: 'true'
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -486,41 +536,41 @@ jobs:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token" secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets - name: Decrypt secrets
env: env:
ACCOUNT_NAME: bitwardenci DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
CONTAINER_NAME: profiles
run: | run: |
mkdir -p $HOME/secrets mkdir -p ~/secrets
profiles=(
"dist_autofill.mobileprovision"
"dist_bitwarden.mobileprovision"
"dist_extension.mobileprovision"
"dist_share_extension.mobileprovision"
"dist_bitwarden_watch_app.mobileprovision"
"dist_bitwarden_watch_app_extension.mobileprovision"
)
for FILE in "${profiles[@]}" gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
do --output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--file $HOME/secrets/$FILE --output none --output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
done gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
- name: Download Google Services secret gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
env: --output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
ACCOUNT_NAME: bitwardenci gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
CONTAINER_NAME: mobile --output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
FILE: GoogleService-Info.plist gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
run: | --output $HOME/secrets/dist_share_extension.mobileprovision \
mkdir -p $HOME/secrets ./.github/secrets/dist_share_extension.mobileprovision.gpg
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--file src/watchOS/bitwarden/$FILE --output none --output $HOME/secrets/dist_watch_app.mobileprovision \
./.github/secrets/dist_watch_app.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER)) BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "########################################"
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "########################################"
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>/' ./${{ 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.Extension/Info.plist
@@ -531,26 +581,26 @@ jobs:
- name: Update Entitlements - name: Update Entitlements
run: | run: |
echo "########################################"
echo "##### Updating Entitlements" echo "##### Updating Entitlements"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist echo "########################################"
- name: Get certificates perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
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 - name: Set up Keychain
env: env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: | run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain security set-keychain-settings -lut 1200 build.keychain
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \ -T /usr/bin/codesign -T /usr/bin/security
-T /usr/bin/security security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles - name: Set up provisioning profiles
@@ -559,8 +609,8 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app.mobileprovision WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app_extension.mobileprovision WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH" mkdir -p "$PROFILES_DIR_PATH"
@@ -588,50 +638,74 @@ jobs:
- name: Bulid WatchApp - name: Bulid WatchApp
run: | run: |
echo "########################################"
echo "##### Build WatchApp with Release Configuration" echo "##### Build WatchApp with Release Configuration"
echo "########################################"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "########################################"
echo "##### Done"
echo "########################################"
- name: Archive Build for App Store - name: Archive Build for App Store
run: | run: |
echo "##### Archive for Release ios-arm64" Write-Output "########################################"
Write-Output "##### Archive for Release ios-arm64
Write-Output "########################################"
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 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 "########################################"
Write-Output "##### Done"
Write-Output "########################################"
shell: pwsh
- name: Archive Build for Mobile Automation - name: Archive Build for Mobile Automation
run: | run: |
echo "##### Archive Debug for iossimulator-x64" Write-Output "########################################"
Write-Output "##### Archive Debug for iossimulator-x64
Write-Output "########################################"
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 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
ls $HOME/Library/Developer/Xcode/Archives
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
shell: pwsh
- name: Export .ipa for App Store - name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: | run: |
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive" ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
EXPORT_PATH="./bitwarden-export"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \ xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH -exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI - 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: | run: |
ARCHIVE_PATH="./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64"
EXPORT_PATH="./bitwarden-export"
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Copy all dSYMs files to upload - 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: | run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs" ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
EXPORT_PATH="./bitwarden-export"
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/"
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
- name: Upload App Store .ipa & dSYMs artifacts - name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: Bitwarden iOS name: Bitwarden iOS
path: | path: |
@@ -640,7 +714,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Upload .app file for Automation CI - name: Upload .app file for Automation CI
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with: with:
name: ${{ env.app_ci_output_filename }}.app.zip name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
@@ -674,7 +748,10 @@ jobs:
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
run: | run: |
echo "########################################"
echo "##### Uploading Watch dSYMs to Firebase" echo "##### Uploading Watch dSYMs to Firebase"
echo "########################################"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \; 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 - name: Validate app in App Store
@@ -690,6 +767,7 @@ jobs:
run: | run: |
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \ xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD" --username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store - name: Deploy to App Store
if: | if: |
@@ -721,7 +799,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -733,7 +811,7 @@ jobs:
secrets: "crowdin-api-token" secrets: "crowdin-api-token"
- name: Upload Sources - name: Upload Sources
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0 uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -757,14 +835,30 @@ jobs:
steps: steps:
- name: Check if any job failed - name: Check if any job failed
if: | if: |
(github.ref == 'refs/heads/main' (github.ref == 'refs/heads/main')
|| github.ref == 'refs/heads/rc' || (github.ref == 'refs/heads/rc')
|| github.ref == 'refs/heads/hotfix-rc') || (github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure') env:
run: exit 1 CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }}
F_DROID_STATUS: ${{ needs.f-droid.result }}
IOS_STATUS: ${{ needs.ios.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$ANDROID_STATUS" = "failure" ]; then
exit 1
elif [ "$F_DROID_STATUS" = "failure" ]; then
exit 1
elif [ "$IOS_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
- name: Check if a RC branch exists
id: branch-check
run: |
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
elif [[ "${rc_branch_check}" -gt 0 ]]; then
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=rc" >> $GITHUB_OUTPUT
fi
- name: Delete RC branch
env:
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
run: |
if ! [[ -z "$BRANCH_NAME" ]]; then
git push --quiet origin --delete $BRANCH_NAME
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
fi

View File

@@ -15,10 +15,10 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -30,7 +30,7 @@ jobs:
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations - name: Download translations
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0 uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -28,7 +28,7 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }} branch-name: ${{ steps.branch.outputs.branch-name }}
steps: steps:
- name: Branch check - name: Branch check
if: inputs.release_type != 'Dry Run' if: github.event.inputs.release_type != 'Dry Run'
run: | run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "===================================" echo "==================================="
@@ -38,15 +38,15 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Check Release Version - name: Check Release Version
id: version id: version
uses: bitwarden/gh-actions/release-version-check@main uses: bitwarden/gh-actions/release-version-check@main
with: with:
release-type: ${{ inputs.release_type }} release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin project-type: xamarin
file: src/App/Platforms/Android/AndroidManifest.xml file: src/Android/Properties/AndroidManifest.xml
- name: Get branch name - name: Get branch name
id: branch id: branch
@@ -55,8 +55,8 @@ jobs:
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create GitHub deployment - name: Create GitHub deployment
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
id: deployment id: deployment
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
@@ -67,16 +67,16 @@ jobs:
- name: Download all artifacts - name: Download all artifacts
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }} branch: ${{ steps.branch.outputs.branch-name }}
- name: Dry Run - Download all artifacts - name: Dry Run - Download all artifacts
if: ${{ inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -86,8 +86,8 @@ jobs:
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release - name: Create release
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
with: with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
@@ -103,16 +103,16 @@ jobs:
draft: true draft: true
- name: Update deployment status to Success - name: Update deployment status to Success
if: ${{ inputs.release_type != 'Dry Run' && success() }} if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success' state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }} deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure - name: Update deployment status to Failure
if: ${{ inputs.release_type != 'Dry Run' && failure() }} if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure' state: 'failure'
@@ -126,11 +126,11 @@ jobs:
if: inputs.fdroid_publish if: inputs.fdroid_publish
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Download F-Droid .apk artifact - name: Download F-Droid .apk artifact
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -138,8 +138,8 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - Download F-Droid .apk artifact - name: Dry Run - Download F-Droid .apk artifact
if: ${{ inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -147,7 +147,7 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node - name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with: with:
node-version: '16.x' node-version: '16.x'
@@ -176,19 +176,13 @@ jobs:
- name: Install Node dependencies - name: Install Node dependencies
run: npm install run: npm install
- name: Login to Azure - CI Subscription - name: Decrypt secrets
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
env: env:
ACCOUNT_NAME: bitwardenci DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
CONTAINER_NAME: mobile
run: | run: |
mkdir -p $HOME/secrets mkdir -p ~/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--name store_fdroid-keystore.jks --file ./store/fdroid/keystore.jks --output none --output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
- name: Compile for F-Droid Store - name: Compile for F-Droid Store
env: env:
@@ -217,5 +211,5 @@ jobs:
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages - name: Deploy to gh-pages
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm run deploy run: npm run deploy

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: 'Run stale action' - name: 'Run stale action'
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
with: with:
stale-issue-label: 'needs-reply' stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes' stale-pr-label: 'needs-changes'

View File

@@ -1,5 +1,5 @@
--- ---
name: Auto Bump Mobile Version name: Version Auto Bump
on: on:
push: push:
@@ -7,25 +7,34 @@ on:
- v** - v**
jobs: jobs:
bump-version: setup:
name: Bump Mobile Version name: "Setup"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs:
version_number: ${{ steps.version.outputs.new-version }}
steps: steps:
- name: Login to Azure - CI Subscription - name: Checkout Branch
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve bot secrets - name: Calculate bumped version
id: retrieve-bot-secrets id: version
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Trigger Version Bump workflow
env: env:
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} RELEASE_TAG: ${{ github.ref }}
run: | run: |
echo '{"cut_rc_branch": "false"}' | \ CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
gh workflow run version-bump.yml --json --repo bitwarden/mobile 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
trigger_version_bump:
name: Bump version to ${{ needs.setup.outputs.version_number }}
needs: setup
uses: ./.github/workflows/version-bump.yml
with:
version_number: ${{ needs.setup.outputs.version_number }}
secrets: inherit

View File

@@ -1,13 +1,13 @@
--- ---
name: Version Bump name: Version Bump
run-name: Version Bump - v${{ inputs.version_number }}
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version_number_override: version_number:
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')" description: "New version (example: '2024.1.0')"
required: false required: true
type: string
cut_rc_branch: cut_rc_branch:
description: "Cut RC branch?" description: "Cut RC branch?"
default: true default: true
@@ -15,34 +15,11 @@ on:
jobs: jobs:
bump_version: bump_version:
name: Bump Version name: "Bump Version to v${{ inputs.version_number }}"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs:
version: ${{ steps.set-final-version-output.outputs.version }}
steps: steps:
- name: Validate version input
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-check@main
with:
version: ${{ inputs.version_number_override }}
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Check if RC branch exists
if: ${{ inputs.cut_rc_branch == true }}
run: |
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
echo "Remote RC branch exists."
echo "Please delete current RC branch before running again."
exit 1
fi
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -55,46 +32,39 @@ jobs:
github-gpg-private-key-passphrase, github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope" github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout Branch
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
ref: main
repository: bitwarden/mobile
- name: Import GPG key - name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
with: with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true git_user_signingkey: true
git_commit_gpgsign: true git_commit_gpgsign: true
- name: 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 - name: Create Version Branch
id: create-branch id: create-branch
run: | run: |
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d") NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
git switch -c $NAME git switch -c $NAME
echo "name=$NAME" >> $GITHUB_OUTPUT echo "name=$NAME" >> $GITHUB_OUTPUT
- name: Install xmllint - name: Install xmllint
run: | run: sudo apt install -y libxml2-utils
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Get current version - name: Verify input version
id: current-version env:
NEW_VERSION: ${{ inputs.version_number }}
run: | run: |
CURRENT_VERSION=$(xmllint --xpath ' CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName" string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"]) and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml) ' src/Android/Properties/AndroidManifest.xml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
- name: Verify input version
if: ${{ inputs.version_number_override != '' }}
env:
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number_override }}
run: |
# Error if version has not changed. # Error if version has not changed.
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
echo "Version has not changed." echo "Version has not changed."
@@ -110,93 +80,40 @@ jobs:
exit 1 exit 1
fi fi
- name: Calculate next release version - name: Bump Version - Android XML
if: ${{ inputs.version_number_override == '' }}
id: calculate-next-version
uses: bitwarden/gh-actions/version-next@main
with:
version: ${{ steps.current-version.outputs.version }}
- name: Bump Version - Android XML - Version Override
if: ${{ inputs.version_number_override != '' }}
id: bump-version-override
uses: bitwarden/gh-actions/version-bump@main uses: bitwarden/gh-actions/version-bump@main
with: with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml" version: ${{ inputs.version_number }}
version: ${{ inputs.version_number_override }} file_path: "src/Android/Properties/AndroidManifest.xml"
- name: Bump Version - Android XML - Automatic Calculation - name: Bump Version - iOS.Autofill
if: ${{ inputs.version_number_override == '' }}
id: bump-version-automatic
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Autofill - Version Override
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-bump@main uses: bitwarden/gh-actions/version-bump@main
with: with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Autofill/Info.plist" file_path: "src/iOS.Autofill/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Autofill - Automatic Calculation - name: Bump Version - iOS.Extension
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Extension - Version Override
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-bump@main uses: bitwarden/gh-actions/version-bump@main
with: with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Extension/Info.plist" file_path: "src/iOS.Extension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Extension - Automatic Calculation - name: Bump Version - iOS.ShareExtension
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Extension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.ShareExtension - Version Override
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-bump@main uses: bitwarden/gh-actions/version-bump@main
with: with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.ShareExtension/Info.plist" file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.ShareExtension - Automatic Calculation - name: Bump Version - iOS
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main uses: bitwarden/gh-actions/version-bump@main
with: with:
file_path: "src/iOS.ShareExtension/Info.plist" version: ${{ inputs.version_number }}
version: ${{ steps.calculate-next-version.outputs.version }} file_path: "src/iOS/Info.plist"
- name: Bump Version - iOS - Version Override - name: Setup git
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Set Job output
id: set-final-version-output
run: | run: |
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT git config --local user.name "bitwarden-devops-bot"
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
fi
- name: Check if version changed - name: Check if version changed
id: version-changed id: version-changed
@@ -210,7 +127,7 @@ jobs:
- name: Commit files - name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
- name: Push changes - name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
@@ -224,7 +141,7 @@ jobs:
env: env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_BRANCH: ${{ steps.create-branch.outputs.name }} 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: | run: |
PR_URL=$(gh pr create --title "$TITLE" \ PR_URL=$(gh pr create --title "$TITLE" \
--base "main" \ --base "main" \
@@ -240,58 +157,24 @@ jobs:
- [X] Other - [X] Other
## Objective ## 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 echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
- name: Approve PR - name: Approve PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr review $PR_NUMBER --approve run: gh pr review $PR_NUMBER --approve
- name: Merge PR - name: Merge PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env: env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
cut_rc: cut_rc:
name: Cut RC branch name: Cut RC Branch
if: ${{ inputs.cut_rc_branch == true }} if: ${{ inputs.cut_rc_branch == true }}
needs: bump_version needs: bump_version
runs-on: ubuntu-22.04 uses: ./.github/workflows/_cut_rc.yml
steps: secrets: inherit
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Verify version has been updated
env:
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
run: |
# Wait for version to change.
while : ; do
echo "Waiting for version to be updated..."
git pull --force
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
# If the versions don't match we continue the loop, otherwise we break out of the loop.
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
sleep 10
done
- name: Cut RC branch
run: |
git switch --quiet --create rc
git push --quiet --set-upstream origin rc

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main

1
.gitignore vendored
View File

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

View File

@@ -1,16 +0,0 @@
<Project>
<PropertyGroup>
<MauiVersion>8.0.7</MauiVersion>
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
<IncludeBitwardenWatchOSApp>True</IncludeBitwardenWatchOSApp>
<Argon2IdLoadMtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</Argon2IdLoadMtouchExtraArgs>
<!-- Uncomment this when Unit Testing-->
<!-- <CustomConstants>UT</CustomConstants> -->
<!-- Uncomment this when building FDROID-->
<!-- <CustomConstants>FDROID</CustomConstants> -->
</PropertyGroup>
</Project>

View File

@@ -1,4 +1,4 @@
[![Github Workflow build on main](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:main) [![Github Workflow build on master](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
@@ -6,7 +6,7 @@
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a> <a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
The Bitwarden mobile application is written in C# using .NET MAUI. The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
@@ -20,6 +20,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
# Contribute # Contribute
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution. Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file. Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.

View File

@@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34112.27
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
EndProject EndProject
@@ -15,14 +15,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -35,7 +27,6 @@ Global
AppStore|iPhone = AppStore|iPhone AppStore|iPhone = AppStore|iPhone
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
Ad-Hoc|iPhone = Ad-Hoc|iPhone Ad-Hoc|iPhone = Ad-Hoc|iPhone
FDroid|Any CPU = FDroid|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -60,8 +51,6 @@ Global
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -82,8 +71,6 @@ Global
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -104,8 +91,6 @@ Global
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -126,8 +111,6 @@ Global
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -148,8 +131,6 @@ Global
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -162,52 +143,6 @@ Global
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -220,14 +155,4 @@ Global
$0.DotNetNamingPolicy = $1 $0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = PrefixedHierarchical $1.DirectoryNamespaceAssociation = PrefixedHierarchical
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{971FDF07-E288-4239-B47A-E9E7E912193B} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{83449CC4-1F76-4CFE-92B1-D2E13A62506F} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{E71F3053-056C-4381-9638-048ED73BDFF6} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -2,9 +2,9 @@ project_id_env: _CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN api_token_env: CROWDIN_API_TOKEN
preserve_hierarchy: true preserve_hierarchy: true
files: files:
- source: /src/Core/Resources/Localization/AppResources.resx - source: /src/App/Resources/AppResources.resx
dest: /src/Core/Resources/Localization/%original_file_name% dest: /src/App/Resources/%original_file_name%
translation: /src/Core/Resources/Localization/AppResources.%two_letters_code%.resx translation: /src/App/Resources/AppResources.%two_letters_code%.resx
update_option: update_as_unapproved update_option: update_as_unapproved
languages_mapping: languages_mapping:
two_letters_code: two_letters_code:

View File

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

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
<add key="Local AndroidX Credentials" value="lib/android/Xamarin.AndroidX.Credentials" />
</packageSources>
</configuration>

2
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "bitwarden-mobile", "name": "bitwarden-mobile",
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"gh-pages": "3.2.3" "gh-pages": "^3.2.3"
} }
}, },
"node_modules/array-union": { "node_modules/array-union": {

View File

@@ -6,6 +6,6 @@
"clean:l10n": "git push origin --delete l10n_master" "clean:l10n": "git push origin --delete l10n_master"
}, },
"devDependencies": { "devDependencies": {
"gh-pages": "3.2.3" "gh-pages": "^3.2.3"
} }
} }

View File

@@ -56,20 +56,18 @@
<CodesignKey>iPhone Developer</CodesignKey> <CodesignKey>iPhone Developer</CodesignKey>
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements> <CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
<UseInterpreter>true</UseInterpreter> <UseInterpreter>true</UseInterpreter>
</PropertyGroup> <!--TODO: add argon2id load when library is built with the corresponding architecture for iOS Simulator-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|iossimulator-x64'">
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'"> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs> <MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage> <CreatePackage>false</CreatePackage>
<CodesignProvision>$(ReleaseCodesignProvision)</CodesignProvision> <CodesignProvision>Automatic:AppStore</CodesignProvision>
<CodesignKey>$(ReleaseCodesignKey)</CodesignKey> <CodesignKey>iPhone Distribution</CodesignKey>
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements> <CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
<UseInterpreter>true</UseInterpreter> <UseInterpreter>true</UseInterpreter>
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs> <MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<!--This is needed for PCLCrypto to work correctly--> <!--This is needed for PCLCrypto to work correctly-->
@@ -121,7 +119,6 @@
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" /> <PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" /> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
@@ -158,15 +155,6 @@
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" /> <BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" /> <BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" /> <BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
<BundleResource Include="Platforms\iOS\Resources\logo.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white%402x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert%402x.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\logo%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white.png" />
<BundleResource Include="Platforms\iOS\Resources\logo%402x.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" /> <ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
@@ -206,12 +194,15 @@
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF"> <MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
<BaseSize>24,24</BaseSize> <BaseSize>24,24</BaseSize>
</MauiImage> </MauiImage>
<MauiImage Include="Resources\search.svg" TintColor="#FFFFFFFF">
<BaseSize>24,24</BaseSize>
</MauiImage>
<MauiImage Include="Resources\send.svg"> <MauiImage Include="Resources\send.svg">
<BaseSize>24,24</BaseSize> <BaseSize>24,24</BaseSize>
</MauiImage> </MauiImage>
<MauiImage Include="Resources\yubikey.png" /> <MauiImage Include="Resources\yubikey.png" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' AND '$(IncludeBitwardeniOSExtensions)' == 'True'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj"> <ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
<IsAppExtension>true</IsAppExtension> <IsAppExtension>true</IsAppExtension>
<IsWatchApp>false</IsWatchApp> <IsWatchApp>false</IsWatchApp>
@@ -225,7 +216,7 @@
<IsWatchApp>false</IsWatchApp> <IsWatchApp>false</IsWatchApp>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND '$(IncludeBitwardenWatchOSApp)' == 'True'"> <PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath> <WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath> <WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
<WatchAppBundle>Bitwarden.app</WatchAppBundle> <WatchAppBundle>Bitwarden.app</WatchAppBundle>
@@ -247,15 +238,4 @@
<GoogleServicesJson Include="Platforms\Android\google-services.json" /> <GoogleServicesJson Include="Platforms\Android\google-services.json" />
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" /> <GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Platforms\iOS\Resources\logo.png" />
<None Remove="Platforms\iOS\Resources\logo_white%402x.png" />
<None Remove="Platforms\iOS\Resources\more_vert%402x.png" />
<None Remove="Platforms\iOS\Resources\logo_white%403x.png" />
<None Remove="Platforms\iOS\Resources\logo%403x.png" />
<None Remove="Platforms\iOS\Resources\more_vert%403x.png" />
<None Remove="Platforms\iOS\Resources\more_vert.png" />
<None Remove="Platforms\iOS\Resources\logo_white.png" />
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,25 @@
#if IOS || MACCATALYST
using PlatformView = WebKit.WKWebView;
#elif ANDROID
using PlatformView = Android.Webkit.WebView;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using Bit.App.Controls;
using Microsoft.Maui.Handlers;
namespace Bit.App.Handlers
{
public partial class HybridWebViewHandler
{
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
}
}

View File

@@ -13,6 +13,7 @@
}, },
handlers => handlers =>
{ {
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
#if ANDROID #if ANDROID
Bit.App.Handlers.EntryHandlerMappings.Setup(); Bit.App.Handlers.EntryHandlerMappings.Setup();
Bit.App.Handlers.EditorHandlerMappings.Setup(); Bit.App.Handlers.EditorHandlerMappings.Setup();
@@ -27,7 +28,6 @@
Bit.App.Handlers.ButtonHandlerMappings.Setup(); Bit.App.Handlers.ButtonHandlerMappings.Setup();
Bit.App.Handlers.ToolbarHandlerMappings.Setup(); Bit.App.Handlers.ToolbarHandlerMappings.Setup();
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler)); handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler)); handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
#else #else

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.3.3" android:installLocation="internalOnly" package="com.x8bit.bitwarden"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.12.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
@@ -43,9 +43,6 @@
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) --> <!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android --> <!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<queries> <queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<data android:scheme="http" /> <data android:scheme="http" />

View File

@@ -347,7 +347,7 @@ namespace Bit.Droid.Autofill
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the // InlinePresentation requires nonNull pending intent (even though we only utilize one for the
// "my vault" presentation) so we're including an empty one here // "my vault" presentation) so we're including an empty one here
pendingIntent = PendingIntent.GetService(context, 0, new Intent(), pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, false)); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
} }
var slice = CreateInlinePresentationSlice( var slice = CreateInlinePresentationSlice(
inlinePresentationSpec, inlinePresentationSpec,

View File

@@ -1,9 +0,0 @@
namespace Bit.Droid.Autofill
{
public class CredentialProviderConstants
{
public const string CredentialProviderCipherId = "credentialProviderCipherId";
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
public const string CredentialIdIntentExtra = "credId";
}
}

View File

@@ -1,65 +0,0 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformPasskeyAuthAsync(cipherId).FireAndForget();
}
private async Task GetCipherAndPerformPasskeyAuthAsync(string cipherId)
{
// TODO this is a work in progress
// https://developer.android.com/training/sign-in/credential-provider#passkeys-implement
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
// var publicKeyRequest = getRequest?.CredentialOptions as PublicKeyCredentialRequestOptions;
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
var credIdEnc = requestInfo?.GetString(CredentialProviderConstants.CredentialIdIntentExtra);
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var passkey = decCipher.Login.Fido2Credentials.Find(f => f.CredentialId == credIdEnc);
var credId = Convert.FromBase64String(credIdEnc);
// var privateKey = Convert.FromBase64String(passkey.PrivateKey);
// var uid = Convert.FromBase64String(passkey.uid);
var origin = getRequest?.CallingAppInfo.Origin;
var packageName = getRequest?.CallingAppInfo.PackageName;
// --- continue WIP here (save TOTP copy as last step) ---
// Copy TOTP if needed
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@@ -1,147 +0,0 @@
using Android;
using Android.App;
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using AndroidX.Credentials.Provider;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Models.View;
using Resource = Microsoft.Maui.Resource;
namespace Bit.Droid.Autofill
{
[Service(Permission = Manifest.Permission.BindCredentialProviderService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.service.credentials.CredentialProviderService" })]
[MetaData("android.credentials.provider", Resource = "@xml/provider")]
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
{
private const string GetPasskeyIntentAction = "PACKAGE_NAME.GET_PASSKEY";
private const int UniqueRequestCode = 94556023;
private ICipherService _cipherService;
private IUserVerificationService _userVerificationService;
private IVaultTimeoutService _vaultTimeoutService;
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
_vaultTimeoutService ??= ServiceContainer.Resolve<IVaultTimeoutService>();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked)
{
var response = await ProcessGetCredentialsRequestAsync(request);
callback.OnResult(response);
}
// TODO handle auth/unlock account flow
}
catch (GetCredentialException e)
{
_logger.Value.Exception(e);
callback.OnError(e.ErrorMessage ?? "Error getting credentials");
}
catch (Exception e)
{
_logger.Value.Exception(e);
throw;
}
}
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
BeginGetCredentialRequest request)
{
IList<CredentialEntry> credentialEntries = null;
foreach (var option in request.BeginGetCredentialOptions)
{
var credentialOption = option as BeginGetPublicKeyCredentialOption;
if (credentialOption != null)
{
credentialEntries ??= new List<CredentialEntry>();
((List<CredentialEntry>)credentialEntries).AddRange(
await PopulatePasskeyDataAsync(request.CallingAppInfo, credentialOption));
}
}
if (credentialEntries == null)
{
return new BeginGetCredentialResponse();
}
return new BeginGetCredentialResponse.Builder()
.SetCredentialEntries(credentialEntries)
.Build();
}
private async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
BeginGetPublicKeyCredentialOption option)
{
var packageName = callingAppInfo.PackageName;
var origin = callingAppInfo.Origin;
var signingInfo = callingAppInfo.SigningInfo;
var request = new PublicKeyCredentialRequestOptions(option.RequestJson);
var passkeyEntries = new List<CredentialEntry>();
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
var ciphers = await _cipherService.GetAllDecryptedForUrlAsync(origin);
if (ciphers == null)
{
return passkeyEntries;
}
var passkeyCiphers = ciphers.Where(cipher => cipher.HasFido2Credential).ToList();
if (!passkeyCiphers.Any())
{
return passkeyEntries;
}
foreach (var cipher in passkeyCiphers)
{
var passkeyEntry = GetPasskey(cipher, option);
passkeyEntries.Add(passkeyEntry);
}
return passkeyEntries;
}
private PublicKeyCredentialEntry GetPasskey(CipherView cipher, BeginGetPublicKeyCredentialOption option)
{
var credDataBundle = new Bundle();
credDataBundle.PutString(CredentialProviderConstants.CredentialIdIntentExtra,
cipher.Login.MainFido2Credential.CredentialId);
var intent = new Intent(ApplicationContext, typeof(CredentialProviderSelectionActivity))
.SetAction(GetPasskeyIntentAction).SetPackage(Constants.PACKAGE_NAME);
intent.PutExtra(CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
intent.PutExtra(CredentialProviderConstants.CredentialProviderCipherId, cipher.Id);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueRequestCode, intent,
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
return new PublicKeyCredentialEntry.Builder(
ApplicationContext,
cipher.Login.Username ?? "No username",
pendingIntent,
option)
.SetDisplayName(cipher.Name)
.SetIcon(Icon.CreateWithResource(ApplicationContext, Resource.Drawable.icon))
.Build();
}
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
}
}

View File

@@ -1,6 +1,5 @@
using AndroidX.AppCompat.View.Menu; using AndroidX.AppCompat.View.Menu;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Google.Android.Material.BottomNavigation; using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Handlers; using Microsoft.Maui.Handlers;
@@ -91,17 +90,7 @@ namespace Bit.App.Handlers
if(e.Item is MenuItemImpl item) if(e.Item is MenuItemImpl item)
{ {
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot."); System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
MainThread.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync());
{
try
{
await _tabbedPage.CurrentPage.Navigation.PopToRootAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
} }
} }

View File

@@ -6,19 +6,10 @@ using AWebkit = Android.Webkit;
namespace Bit.App.Handlers namespace Bit.App.Handlers
{ {
public class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView> public partial class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
{ {
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}"; private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper) public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
{ {
} }

View File

@@ -2,10 +2,7 @@
using Android.Graphics.Drawables; using Android.Graphics.Drawables;
using Android.OS; using Android.OS;
using AndroidX.Core.Content.Resources; using AndroidX.Core.Content.Resources;
using AndroidX.Core.Graphics;
using Bit.App.Droid.Utilities; using Bit.App.Droid.Utilities;
using Bit.App.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Handlers namespace Bit.App.Handlers
{ {
@@ -40,31 +37,6 @@ namespace Bit.App.Handlers
}; };
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors); handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
}); });
Microsoft.Maui.Handlers.SwitchHandler.Mapper.AppendToMapping(nameof(ISwitch.TrackColor), (handler, mauiSwitch) =>
{
var trackStates = new[]
{
new[] { Android.Resource.Attribute.StateChecked }, // checked
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
};
var selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.5f);
var unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.7f);
if (ThemeManager.UsingLightTheme)
{
selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.White.ToPlatform().ToArgb(), 0.7f);
unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.3f);
}
var trackColors = new int[]
{
selectedColor,
unselectedColor
};
handler.PlatformView.TrackTintList = new ColorStateList(trackStates, trackColors);
});
} }
} }
} }

View File

@@ -74,11 +74,6 @@ namespace Bit.Droid
// this needs to be called here before base.OnCreate(...) // this needs to be called here before base.OnCreate(...)
Intent?.Validate(); Intent?.Validate();
//We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs
_appOptions = GetOptions();
//This does not replace existing Options in App.xaml.cs if it exists already. It only updates properties in Options related with Autofill/CreateSend/etc..
((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetAndroidOptions(_appOptions);
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ => _deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
@@ -94,6 +89,7 @@ namespace Bit.Droid
toplayout.FilterTouchesWhenObscured = true; toplayout.FilterTouchesWhenObscured = true;
} }
_appOptions = GetOptions();
CreateNotificationChannel(); CreateNotificationChannel();
DisableAndroidFontScale(); DisableAndroidFontScale();

View File

@@ -85,12 +85,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IWatchDeviceService>(), ServiceContainer.Resolve<IWatchDeviceService>(),
ServiceContainer.Resolve<IConditionedAwaiterManager>()); ServiceContainer.Resolve<IConditionedAwaiterManager>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager); ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
var userPinService = new UserPinService(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<ICryptoService>(),
ServiceContainer.Resolve<IVaultTimeoutService>());
ServiceContainer.Register<IUserPinService>(userPinService);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -164,8 +158,9 @@ namespace Bit.Droid
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>()); platformUtilsService, new LazyResolve<IEventService>());
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService); var biometricService = new BiometricService(stateService, cryptoService);
var userPinService = new UserPinService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage); ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
@@ -189,6 +184,7 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService); ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService); ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool()); ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
ServiceContainer.Register<IUserPinService>(userPinService);
// Push // Push
#if FDROID #if FDROID

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

@@ -79,11 +79,7 @@ namespace Bit.Droid.Services
} }
var context = Android.App.Application.Context; var context = Android.App.Application.Context;
var intent = context.PackageManager?.GetLaunchIntentForPackage(context.PackageName ?? string.Empty); var intent = new Intent(context, typeof(MainActivity));
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId);
if(intent != null && context.PackageManager != null && !string.IsNullOrEmpty(context.PackageName))
{
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true); var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags); var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
@@ -92,14 +88,13 @@ namespace Bit.Droid.Services
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags); var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
builder.SetContentIntent(pendingIntent) var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
.SetDeleteIntent(deletePendingIntent); .SetContentIntent(pendingIntent)
} .SetContentTitle(title)
builder.SetContentTitle(title)
.SetContentText(message) .SetContentText(message)
.SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification) .SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification)
.SetColor((int)Android.Graphics.Color.White) .SetColor((int)Android.Graphics.Color.White)
.SetDeleteIntent(deletePendingIntent)
.SetAutoCancel(true); .SetAutoCancel(true);
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0) if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)

View File

@@ -37,23 +37,6 @@ namespace Bit.Droid.Services
_eventService = eventService; _eventService = eventService;
} }
public bool CredentialProviderServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
return false;
}
try
{
// TODO - find a way to programmatically check if the credential provider service is enabled
return false;
}
catch
{
return false;
}
}
public bool AutofillServiceEnabled() public bool AutofillServiceEnabled()
{ {
if (Build.VERSION.SdkInt < BuildVersionCodes.O) if (Build.VERSION.SdkInt < BuildVersionCodes.O)
@@ -180,14 +163,7 @@ namespace Bit.Droid.Services
return Accessibility.AccessibilityHelpers.OverlayPermitted(); return Accessibility.AccessibilityHelpers.OverlayPermitted();
} }
public void DisableCredentialProviderService()
{
try
{
// TODO - find a way to programmatically disable the provider service, or take the user to the settings page where they can do it
}
catch { }
}
public void DisableAutofillService() public void DisableAutofillService()
{ {

View File

@@ -11,7 +11,6 @@ using Android.Text.Method;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Widget; using Android.Widget;
using AndroidX.Credentials;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
@@ -72,8 +71,6 @@ namespace Bit.Droid.Services
} }
public bool LaunchApp(string appName) public bool LaunchApp(string appName)
{
try
{ {
if ((int)Build.VERSION.SdkInt < 33) if ((int)Build.VERSION.SdkInt < 33)
{ {
@@ -87,15 +84,6 @@ namespace Bit.Droid.Services
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null); launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null; return launchIntentSender != null;
} }
catch (IntentSender.SendIntentException)
{
return false;
}
catch (Android.Util.AndroidException)
{
return false;
}
}
public async Task ShowLoadingAsync(string text) public async Task ShowLoadingAsync(string text)
{ {
@@ -502,27 +490,6 @@ namespace Bit.Droid.Services
} }
} }
public void OpenCredentialProviderSettings()
{
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var pendingIntent = CredentialManager.Create(activity).CreateSettingsPendingIntent();
pendingIntent.Send();
}
catch (ActivityNotFoundException)
{
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetMessage(AppResources.BitwardenCredentialProviderGoToSettings);
alertBuilder.SetCancelable(true);
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
{
(sender as AlertDialog)?.Cancel();
});
alertBuilder.Create().Show();
}
}
public void OpenAccessibilitySettings() public void OpenAccessibilitySettings()
{ {
try try
@@ -581,8 +548,6 @@ namespace Bit.Droid.Services
return true; return true;
} }
public bool SupportsCredentialProviderService() => Build.VERSION.SdkInt >= BuildVersionCodes.UpsideDownCake;
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O; public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R; public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;

View File

@@ -8,7 +8,6 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Accessibility; using Bit.Droid.Accessibility;
using Java.Lang; using Java.Lang;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
{ {
@@ -77,7 +76,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(AccessibilityActivity)); var intent = new Intent(this, typeof(AccessibilityActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("autofillTileClicked", true); intent.PutExtra("autofillTileClicked", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: true); StartActivityAndCollapse(intent);
} }
private void ShowConfigErrorDialog() private void ShowConfigErrorDialog()

View File

@@ -1,8 +1,15 @@
using Android.App; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Service.QuickSettings; using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities; using Android.Views;
using Android.Widget;
using Java.Lang; using Java.Lang;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
@@ -55,7 +62,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity)); var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("generatorTile", true); intent.PutExtra("generatorTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false); StartActivityAndCollapse(intent);
} }
} }
} }

View File

@@ -1,8 +1,15 @@
using Android.App; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Service.QuickSettings; using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities; using Android.Views;
using Android.Widget;
using Java.Lang; using Java.Lang;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
@@ -56,7 +63,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity)); var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("myVaultTile", true); intent.PutExtra("myVaultTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false); StartActivityAndCollapse(intent);
} }
} }
} }

View File

@@ -2,7 +2,6 @@
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Provider; using Android.Provider;
using Android.Service.QuickSettings;
using Bit.App.Utilities; using Bit.App.Utilities;
namespace Bit.App.Droid.Utilities namespace Bit.App.Droid.Utilities
@@ -65,26 +64,5 @@ namespace Bit.App.Droid.Utilities
return pendingIntentFlags; return pendingIntentFlags;
} }
public static void StartActivityAndCollapseWithIntent(this TileService service, Intent intent, bool isMutable)
{
//For Android 14+ We need to use PendingIntent instead of Intent directly. Older versions still need to use Intent.
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
service.StartActivityAndCollapse(intent);
return;
}
var pendingIntent = PendingIntent.GetActivity(
service.ApplicationContext,
0,
intent,
AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, isMutable)
);
if (pendingIntent == null)
{
return;
}
service.StartActivityAndCollapse(pendingIntent);
}
} }
} }

View File

@@ -15,7 +15,6 @@ using CoreNFC;
using Foundation; using Foundation;
using Microsoft.Maui.Platform; using Microsoft.Maui.Platform;
using UIKit; using UIKit;
using UserNotifications;
using WatchConnectivity; using WatchConnectivity;
namespace Bit.iOS namespace Bit.iOS
@@ -42,11 +41,9 @@ namespace Bit.iOS
private IStateService _stateService; private IStateService _stateService;
private IEventService _eventService; private IEventService _eventService;
private readonly LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>(); private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
public override bool FinishedLaunching(UIApplication app, NSDictionary options) public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
try
{ {
InitApp(); InitApp();
@@ -57,6 +54,10 @@ namespace Bit.iOS
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
//LoadApplication(new App.App(null));
//iOSCoreHelpers.AppearanceAdjustments();
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
ConnectToWatchIfNeededAsync().FireAndForget(); ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) => _broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
@@ -73,14 +74,14 @@ namespace Bit.iOS
} }
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{ {
await MainThread.InvokeOnMainThreadAsync(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
iOSCoreHelpers.AppearanceAdjustments(); iOSCoreHelpers.AppearanceAdjustments();
}); });
} }
else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen) else if (message.Command == "listenYubiKeyOTP")
{ {
iOSCoreHelpers.ListenYubiKey(listen, _deviceActionService, _nfcSession, _nfcDelegate); iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
} }
else if (message.Command == "unlocked") else if (message.Command == "unlocked")
{ {
@@ -88,90 +89,88 @@ namespace Bit.iOS
Core.Constants.AutofillNeedsIdentityReplacementKey); Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault()) if (needsAutofillReplacement.GetValueOrDefault())
{ {
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
} }
} }
else if (message.Command == "showAppExtension") else if (message.Command == "showAppExtension")
{ {
await MainThread.InvokeOnMainThreadAsync(() => ShowAppExtension((ExtensionPageViewModel)message.Data)); MainThread.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
} }
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
{ {
if (message.Data is Dictionary<string, object> data && data.TryGetValue("successfully", out var value)) if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{ {
var success = value as bool?; var success = data["successfully"] as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12) if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{ {
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
} }
} }
} }
else if (message.Command == "addedCipher" || message.Command == "editedCipher" || else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher") message.Command == "restoredCipher")
{ {
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0)) if (_deviceActionService.SystemMajorVersion() >= 12)
{ {
return; if (await ASHelpers.IdentitiesCanIncremental())
}
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
{ {
var cipherId = message.Data as string; var cipherId = message.Data as string;
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId)) if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
{ {
var identity = await ASHelpers.GetCipherPasswordIdentityAsync(cipherId); var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
if (identity == null) if (identity == null)
{ {
return; return;
} }
await ASCredentialIdentityStoreExtensions.SaveCredentialIdentitiesAsync(identity); await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return; return;
} }
} }
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
}
} }
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher") else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
{ {
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0)) if (_deviceActionService.SystemMajorVersion() >= 12)
{ {
return; if (await ASHelpers.IdentitiesCanIncremental())
}
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
{ {
var identity = ASHelpers.ToPasswordCredentialIdentity( var identity = ASHelpers.ToCredentialIdentity(
message.Data as Bit.Core.Models.View.CipherView); message.Data as Bit.Core.Models.View.CipherView);
if (identity == null) if (identity == null)
{ {
return; return;
} }
await ASCredentialIdentityStoreExtensions.RemoveCredentialIdentitiesAsync(identity); await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return; return;
} }
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
} }
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0)) }
else if (message.Command == "logout")
{ {
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync(); if (_deviceActionService.SystemMajorVersion() >= 12)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
} }
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher") else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0)) && _deviceActionService.SystemMajorVersion() >= 12)
{ {
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
} }
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND) else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{ {
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync(); var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout) if (timeoutAction == VaultTimeoutAction.Logout)
{ {
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0)) await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
{
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
}
} }
else else
{ {
await ASHelpers.ReplaceAllIdentitiesAsync(); await ASHelpers.ReplaceAllIdentities();
} }
} }
} }
@@ -188,16 +187,8 @@ namespace Bit.iOS
return finishedLaunching; return finishedLaunching;
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override void OnResignActivation(UIApplication uiApplication) public override void OnResignActivation(UIApplication uiApplication)
{
try
{ {
if (UIApplication.SharedApplication.KeyWindow != null) if (UIApplication.SharedApplication.KeyWindow != null)
{ {
@@ -225,71 +216,30 @@ namespace Bit.iOS
} }
base.OnResignActivation(uiApplication); base.OnResignActivation(uiApplication);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override void DidEnterBackground(UIApplication uiApplication) public override void DidEnterBackground(UIApplication uiApplication)
{ {
try _stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
{
if (_stateService != null && _deviceActionService != null)
{
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
_messagingService?.Send("slept"); _messagingService?.Send("slept");
base.DidEnterBackground(uiApplication); base.DidEnterBackground(uiApplication);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override async void OnActivated(UIApplication uiApplication) public override void OnActivated(UIApplication uiApplication)
{
try
{ {
base.OnActivated(uiApplication); base.OnActivated(uiApplication);
if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
await UNUserNotificationCenter.Current.SetBadgeCountAsync(0);
}
else
{
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0; UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
UIApplication.SharedApplication.KeyWindow? UIApplication.SharedApplication.KeyWindow?
.ViewWithTag(SPLASH_VIEW_TAG)? .ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview(); .RemoveFromSuperview();
ThemeManager.UpdateThemeOnPagesAsync(); ThemeManager.UpdateThemeOnPagesAsync();
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
public override void WillEnterForeground(UIApplication uiApplication) public override void WillEnterForeground(UIApplication uiApplication)
{
try
{ {
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND); _messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
base.WillEnterForeground(uiApplication); base.WillEnterForeground(uiApplication);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
[Export("application:openURL:sourceApplication:annotation:")] [Export("application:openURL:sourceApplication:annotation:")]
public bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) public bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
@@ -298,101 +248,51 @@ namespace Bit.iOS
} }
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
try
{ {
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options); return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return false;
}
}
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
UIApplicationRestorationHandler completionHandler) UIApplicationRestorationHandler completionHandler)
{
try
{ {
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler)) if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
{ {
return true; return true;
} }
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
return base.ContinueUserActivity(application, userActivity, completionHandler); return base.ContinueUserActivity(application, userActivity, completionHandler);
} }
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")] [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
try
{ {
_pushHandler?.OnErrorReceived(error); _pushHandler?.OnErrorReceived(error);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
try
{ {
_pushHandler?.OnRegisteredSuccess(deviceToken); _pushHandler?.OnRegisteredSuccess(deviceToken);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
[Export("application:didRegisterUserNotificationSettings:")] [Export("application:didRegisterUserNotificationSettings:")]
public void DidRegisterUserNotificationSettings(UIApplication application, public void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings) UIUserNotificationSettings notificationSettings)
{
try
{ {
application.RegisterForRemoteNotifications(); application.RegisterForRemoteNotifications();
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")] [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler) Action<UIBackgroundFetchResult> completionHandler)
{
try
{ {
_pushHandler?.OnMessageReceived(userInfo); _pushHandler?.OnMessageReceived(userInfo);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
[Export("application:didReceiveRemoteNotification:")] [Export("application:didReceiveRemoteNotification:")]
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
try
{ {
_pushHandler?.OnMessageReceived(userInfo); _pushHandler?.OnMessageReceived(userInfo);
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
public void InitApp() public void InitApp()
{ {
@@ -404,6 +304,17 @@ namespace Bit.iOS
// Migration services // Migration services
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService()); ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
// Note: This might cause a race condition. Investigate more.
//Task.Run(() =>
//{
// FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
// FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
// {
// FadeAnimationEnabled = false,
// FadeAnimationForCachedImages = false
// });
//});
iOSCoreHelpers.RegisterLocalServices(); iOSCoreHelpers.RegisterLocalServices();
RegisterPush(); RegisterPush();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -417,7 +328,7 @@ namespace Bit.iOS
_nfcDelegate = new Core.NFCReaderDelegate((success, message) => _nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message)); _messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(ApplyManagedSettingsAsync); iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
} }
private void RegisterPush() private void RegisterPush()
@@ -461,24 +372,15 @@ namespace Bit.iOS
_eventTimer?.Dispose(); _eventTimer?.Dispose();
_eventTimer = null; _eventTimer = null;
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{
try
{ {
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer => _eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
{ {
_eventService?.UploadEventsAsync().FireAndForget(); var task = Task.Run(() => _eventService.UploadEventsAsync());
}); });
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}); });
} }
private async Task StopEventTimerAsync() private async Task StopEventTimerAsync()
{
try
{ {
_eventTimer?.Invalidate(); _eventTimer?.Invalidate();
_eventTimer?.Dispose(); _eventTimer?.Dispose();
@@ -497,11 +399,6 @@ namespace Bit.iOS
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId); UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0; _eventBackgroundTaskId = 0;
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
private async Task ApplyManagedSettingsAsync() private async Task ApplyManagedSettingsAsync()
{ {

View File

@@ -5,24 +5,15 @@ using Foundation;
using Microsoft.Maui.Handlers; using Microsoft.Maui.Handlers;
using WebKit; using WebKit;
namespace Bit.iOS.Core.Handlers namespace Bit.App.Handlers
{ {
public class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView> public partial class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView>
{ {
private const string JSFunction = private const string JSFunction =
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}"; "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
private WKUserContentController _userController; private WKUserContentController _userController;
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper) public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
{ {
} }

View File

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

View File

@@ -1,27 +0,0 @@
{
"images": [
{
"appearances": [],
"scale": "1x",
"idiom": "universal",
"filename": "search.png"
},
{
"appearances": [],
"scale": "2x",
"idiom": "universal",
"filename": "search@2x.png"
},
{
"appearances": [],
"scale": "3x",
"idiom": "universal",
"filename": "search@3x.png"
}
],
"properties": {},
"info": {
"version": 1,
"author": ""
}
}

View File

@@ -4,7 +4,6 @@ namespace Bit.Core.Abstractions
{ {
public interface IAutofillHandler public interface IAutofillHandler
{ {
bool CredentialProviderServiceEnabled();
bool AutofillServicesEnabled(); bool AutofillServicesEnabled();
bool SupportsAutofillService(); bool SupportsAutofillService();
void Autofill(CipherView cipher); void Autofill(CipherView cipher);
@@ -12,7 +11,6 @@ namespace Bit.Core.Abstractions
bool AutofillAccessibilityServiceRunning(); bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted(); bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled(); bool AutofillServiceEnabled();
void DisableCredentialProviderService();
void DisableAutofillService(); 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.Data;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
@@ -34,7 +37,5 @@ namespace Bit.Core.Abstractions
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId); Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
Task SoftDeleteWithServerAsync(string id); Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id); Task RestoreWithServerAsync(string id);
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
} }
} }

View File

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

View File

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

View File

@@ -63,7 +63,5 @@ namespace Bit.Core.Abstractions
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey); Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null); Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null);
Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey); Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey);
Task<string> HashAsync(string value, CryptoHashAlgorithm hashAlgorithm);
Task<bool> ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key);
} }
} }

View File

@@ -28,7 +28,6 @@ namespace Bit.App.Abstractions
bool SupportsNfc(); bool SupportsNfc();
bool SupportsCamera(); bool SupportsCamera();
bool SupportsFido2(); bool SupportsFido2();
bool SupportsCredentialProviderService();
bool SupportsAutofillServices(); bool SupportsAutofillServices();
bool SupportsInlineAutofill(); bool SupportsInlineAutofill();
bool SupportsDrawOver(); bool SupportsDrawOver();
@@ -37,7 +36,6 @@ namespace Bit.App.Abstractions
void RateApp(); void RateApp();
void OpenAccessibilitySettings(); void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();
void OpenCredentialProviderSettings();
void OpenAutofillSettings(); void OpenAutofillSettings();
long GetActiveTime(); long GetActiveTime();
void CloseMainApp(); void CloseMainApp();

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,35 +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>
/// <returns>The new credential</returns>
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
/// <summary>
/// Allows WebAuthn Relying Party scripts to discover and use an existing public key credential, with the 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>
/// <returns>The asserted credential</returns>
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
}
}

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,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);
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
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 namespace Bit.App.Abstractions
{ {
@@ -9,7 +10,5 @@ namespace Bit.App.Abstractions
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password); Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync(); Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> ShouldByPassMasterPasswordRepromptAsync();
} }
} }

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 namespace Bit.Core.Abstractions
{ {
@@ -26,7 +29,7 @@ namespace Bit.Core.Abstractions
bool SupportsDuo(); bool SupportsDuo();
Task<bool> SupportsBiometricAsync(); Task<bool> SupportsBiometricAsync();
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null); Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false); Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false);
long GetActiveTime(); long GetActiveTime();
} }
} }

View File

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

View File

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

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 namespace Bit.Core.Abstractions
{ {
public interface IUserVerificationService public interface IUserVerificationService
{ {
Task<bool> VerifyUser(string secret, VerificationType verificationType); Task<bool> VerifyUser(string secret, VerificationType verificationType);
Task<bool> VerifyMasterPasswordAsync(string masterPassword);
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false); Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
} }
} }

View File

@@ -9,9 +9,7 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
using Bit.Core.Pages;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -36,8 +34,6 @@ namespace Bit.App
private readonly IAccountsManager _accountsManager; private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ILogger _logger;
private static bool _isResumed; private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App. // these variables are static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests; private static bool _pendingCheckPasswordlessLoginRequests;
@@ -48,99 +44,6 @@ namespace Bit.App
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume // Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
private readonly Queue<Action> _onResumeActions = new Queue<Action>(); private readonly Queue<Action> _onResumeActions = new Queue<Action>();
#if ANDROID
/*
* ** Workaround for our Android crashes when trying to use Autofill **
*
* This workaround works by managing the "Window Creation" ourselves.
* - If we get an AutofillExternalActivity we just create a "dummy" window/navigation page so that the activity can run without crashing. (no visible UI is needed)
* - If we get an FromAutofillFramework/Uri/Otp/CreateSend special Option request we create an Autofill Window
* - For everything else we use the default "mainWindow"
*/
public new static Page MainPage
{
get
{
return CurrentWindow?.Page;
}
set
{
if (CurrentWindow != null)
{
CurrentWindow.Page = value;
}
}
}
/// <summary>
/// Find the Current Active Window. There should only be one at any point in Android
/// </summary>
public static ResumeWindow CurrentWindow
{
get
{
return Application.Current?.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive);
}
}
/// <summary>
/// Allows setting Options from MainActivity before base.OnCreate
/// Note 1: This is only be used by Android due to way it's Lifecycle works
/// Note 2: This method does not replace existing Options in App.xaml.cs if it exists already.
/// It only updates properties in Options related with Autofill/CreateSend/etc..
/// </summary>
/// <param name="appOptions">Options created in Android MainActivity.cs</param>
public void SetAndroidOptions(AppOptions appOptions)
{
if (Options == null)
{
Options = appOptions ?? new AppOptions();
}
else if(appOptions != null)
{
Options.Uri = appOptions.Uri;
Options.MyVaultTile = appOptions.MyVaultTile;
Options.GeneratorTile = appOptions.GeneratorTile;
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
Options.CreateSend = appOptions.CreateSend;
}
}
protected override Window CreateWindow(IActivationState activationState)
{
//When executing from AutofillExternalActivity we don't have "Options" so we need to filter "manually"
//In the AutofillExternalActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
if (activationState != null
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
&& autofillFramework == "true"
&& activationState.State.ContainsKey("autofillFrameworkCipherId"))
{
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
}
_isResumed = true;
return new ResumeWindow(new NavigationPage(new AndroidNavigationRedirectPage(Options)));
}
#else
//iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly
public new static Page MainPage
{
get
{
return Application.Current?.MainPage;
}
set
{
if (Application.Current != null)
{
Application.Current.MainPage = value;
}
}
}
#endif
public App() : this(null) public App() : this(null)
{ {
} }
@@ -164,31 +67,21 @@ namespace Bit.App
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager"); _accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>(); _pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_configService = ServiceContainer.Resolve<IConfigService>(); _configService = ServiceContainer.Resolve<IConfigService>();
_logger = ServiceContainer.Resolve<ILogger>();
_accountsManager.Init(() => Options, this); _accountsManager.Init(() => Options, this);
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
Bootstrap(); Bootstrap();
} _broadcasterService.Subscribe(nameof(App), async (message) =>
private async void BroadcastServiceMessageCallbackAsync(Message message)
{ {
try try
{ {
ArgumentNullException.ThrowIfNull(message);
if (message.Command == "showDialog") if (message.Command == "showDialog")
{ {
var details = message.Data as DialogDetails; var details = message.Data as DialogDetails;
ArgumentNullException.ThrowIfNull(details);
ArgumentNullException.ThrowIfNull(MainPage);
var confirmed = true; var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText; AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction); MainThread.BeginInvokeOnMainThread(async () =>
async Task ShowDialogAction()
{ {
if (!string.IsNullOrWhiteSpace(details.CancelText)) if (!string.IsNullOrWhiteSpace(details.CancelText))
{ {
@@ -200,18 +93,22 @@ namespace Bit.App
await MainPage.DisplayAlert(details.Title, details.Text, confirmText); await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
} }
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
} }
}
#if IOS
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND) else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{ {
ResumedAsync().FireAndForget(); ResumedAsync().FireAndForget();
} }
}
else if (message.Command == "slept") else if (message.Command == "slept")
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{ {
await SleptAsync(); await SleptAsync();
} }
#endif }
else if (message.Command == "migrated") else if (message.Command == "migrated")
{ {
await Task.Delay(1000); await Task.Delay(1000);
@@ -228,13 +125,10 @@ namespace Bit.App
Options.OtpData = new OtpData((string)message.Data); Options.OtpData = new OtpData((string)message.Data);
} }
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction); MainThread.InvokeOnMainThreadAsync(async () =>
async Task ExecuteNavigationAction()
{ {
if (MainPage is TabsPage tabsPage) if (MainPage is TabsPage tabsPage)
{ {
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
while (tabsPage.Navigation.ModalStack.Count > 0) while (tabsPage.Navigation.ModalStack.Count > 0)
{ {
await tabsPage.Navigation.PopModalAsync(false); await tabsPage.Navigation.PopModalAsync(false);
@@ -260,41 +154,31 @@ namespace Bit.App
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE) else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{ {
tabsPage.ResetToVaultPage(); tabsPage.ResetToVaultPage();
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options))); await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
} }
} }
} });
} }
else if (message.Command == "convertAccountToKeyConnector") else if (message.Command == "convertAccountToKeyConnector")
{ {
ArgumentNullException.ThrowIfNull(MainPage); MainThread.BeginInvokeOnMainThread(async () =>
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
async Task NavigateToRemoveMasterPasswordPageAction()
{ {
await MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
} });
} }
else if (message.Command == Constants.ForceUpdatePassword) else if (message.Command == Constants.ForceUpdatePassword)
{ {
ArgumentNullException.ThrowIfNull(MainPage); MainThread.BeginInvokeOnMainThread(async () =>
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
async Task NavigateToUpdateTempPasswordPageAction()
{ {
await MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage())); new NavigationPage(new UpdateTempPasswordPage()));
} });
} }
else if (message.Command == Constants.ForceSetPassword) else if (message.Command == Constants.ForceSetPassword)
{ {
ArgumentNullException.ThrowIfNull(MainPage); await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction); new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
void NavigateToSetPasswordPageAction()
{
MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
}
} }
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
{ {
@@ -315,6 +199,7 @@ namespace Bit.App
{ {
LoggerHelper.LogEvenIfCantBeResolved(ex); LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
});
} }
private async Task CheckPasswordlessLoginRequestsAsync() private async Task CheckPasswordlessLoginRequestsAsync()
@@ -329,6 +214,7 @@ namespace Bit.App
{ {
return; return;
} }
var notification = await _stateService.GetPasswordlessLoginNotificationAsync(); var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null) if (notification == null)
{ {
@@ -398,8 +284,6 @@ namespace Bit.App
public AppOptions Options { get; private set; } public AppOptions Options { get; private set; }
protected override async void OnStart() protected override async void OnStart()
{
try
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnStart"); System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_isResumed = true; _isResumed = true;
@@ -418,33 +302,23 @@ namespace Bit.App
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); _messagingService.Send(Constants.PasswordlessLoginRequestKey);
} }
#if ANDROID if (DeviceInfo.Platform == DevicePlatform.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start // Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null; _vaultTimeoutService.DelayLockAndLogoutMs = null;
#endif }
await _configService.GetAsync(); await _configService.GetAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
} }
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
}
}
#if ANDROID
protected override async void OnSleep() protected override async void OnSleep()
#else
protected override void OnSleep()
#endif
{
try
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false; _isResumed = false;
#if ANDROID if (DeviceInfo.Platform == DevicePlatform.Android)
{
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
@@ -455,18 +329,10 @@ namespace Bit.App
ClearAutofillUri(); ClearAutofillUri();
} }
await SleptAsync(); await SleptAsync();
#endif
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
} }
} }
protected override void OnResume() protected override void OnResume()
{
try
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnResume"); System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_isResumed = true; _isResumed = true;
@@ -474,15 +340,9 @@ namespace Bit.App
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); _messagingService.Send(Constants.PasswordlessLoginRequestKey);
} }
#if ANDROID if (DeviceInfo.Platform == DevicePlatform.Android)
ResumedAsync().FireAndForget();
#endif
}
catch (Exception ex)
{ {
_logger?.Exception(ex); ResumedAsync().FireAndForget();
throw;
} }
} }
@@ -568,23 +428,15 @@ namespace Bit.App
Task.Run(() => Task.Run(() =>
{ {
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{
try
{ {
Options.Uri = null; Options.Uri = null;
if (isLocked) if (isLocked)
{ {
App.MainPage = new NavigationPage(new LockPage()); MainPage = new NavigationPage(new LockPage());
} }
else else
{ {
App.MainPage = new TabsPage(); MainPage = new TabsPage();
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
}); });
}); });
@@ -609,13 +461,10 @@ namespace Bit.App
ThemeManager.SetTheme(Resources); ThemeManager.SetTheme(Resources);
RequestedThemeChanged += (s, a) => RequestedThemeChanged += (s, a) =>
{ {
UpdateThemeAsync().FireAndForget(); UpdateThemeAsync();
}; };
_isResumed = true; _isResumed = true;
#if IOS MainPage = new NavigationPage(new HomePage(Options));
//We only set the MainPage here for iOS. Android is using the CreateWindow override for the initial page.
App.MainPage = new NavigationPage(new HomePage(Options));
#endif
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget(); _accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
} }
@@ -627,8 +476,6 @@ namespace Bit.App
return; return;
} }
Task.Run(async () => Task.Run(async () =>
{
try
{ {
var lastSync = await _syncService.GetLastSyncAsync(); var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30))) if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
@@ -636,11 +483,6 @@ namespace Bit.App
await Task.Delay(1000); await Task.Delay(1000);
await _syncService.FullSyncAsync(false); await _syncService.FullSyncAsync(false);
} }
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}); });
} }
@@ -694,36 +536,36 @@ namespace Bit.App
switch (navTarget) switch (navTarget)
{ {
case NavigationTarget.HomeLogin: case NavigationTarget.HomeLogin:
App.MainPage = new NavigationPage(new HomePage(Options)); MainPage = new NavigationPage(new HomePage(Options));
break; break;
case NavigationTarget.Login: case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams) if (navParams is LoginNavigationParams loginParams)
{ {
App.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options)); MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
} }
break; break;
case NavigationTarget.Lock: case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams) if (navParams is LockNavigationParams lockParams)
{ {
App.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)); MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
} }
else else
{ {
App.MainPage = new NavigationPage(new LockPage(Options)); MainPage = new NavigationPage(new LockPage(Options));
} }
break; break;
case NavigationTarget.Home: case NavigationTarget.Home:
App.MainPage = new TabsPage(Options); MainPage = new TabsPage(Options);
break; break;
case NavigationTarget.AddEditCipher: case NavigationTarget.AddEditCipher:
App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break; break;
case NavigationTarget.AutofillCiphers: case NavigationTarget.AutofillCiphers:
case NavigationTarget.OtpCipherSelection: case NavigationTarget.OtpCipherSelection:
App.MainPage = new NavigationPage(new CipherSelectionPage(Options)); MainPage = new NavigationPage(new CipherSelectionPage(Options));
break; break;
case NavigationTarget.SendAddEdit: case NavigationTarget.SendAddEdit:
App.MainPage = new NavigationPage(new SendAddEditPage(Options)); MainPage = new NavigationPage(new SendAddEditPage(Options));
break; break;
} }
} }

View File

@@ -47,7 +47,6 @@ namespace Bit.Core
public const string ConfigsKey = "configsKey"; public const string ConfigsKey = "configsKey";
public const string DisplayEuEnvironmentFlag = "display-eu-environment"; public const string DisplayEuEnvironmentFlag = "display-eu-environment";
public const string RegionEnvironment = "regionEnvironment"; public const string RegionEnvironment = "regionEnvironment";
public const string DuoCallback = "bitwarden://duo-callback";
/// <summary> /// <summary>
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in /// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
@@ -71,11 +70,10 @@ namespace Bit.Core
public const int Argon2Parallelism = 4; public const int Argon2Parallelism = 4;
public const int MasterPasswordMinimumChars = 12; public const int MasterPasswordMinimumChars = 12;
public const int CipherKeyRandomBytesLength = 64; public const int CipherKeyRandomBytesLength = 64;
public const string CipherKeyEncryptionMinServerVersion = "2024.2.0"; public const string CipherKeyEncryptionMinServerVersion = "2023.9.1";
public const string DefaultFido2CredentialType = "public-key"; public const string DefaultFido2CredentialType = "public-key";
public const string DefaultFido2CredentialAlgorithm = "ECDSA"; public const string DefaultFido2CredentialAlgorithm = "ECDSA";
public const string DefaultFido2CredentialCurve = "P-256"; public const string DefaultFido2CredentialCurve = "P-256";
public const int LatestStateVersion = 7;
public static readonly string[] AndroidAllClearCipherCacheKeys = public static readonly string[] AndroidAllClearCipherCacheKeys =
{ {

View File

@@ -12,14 +12,12 @@
BackgroundColor="#22000000" BackgroundColor="#22000000"
Padding="0" Padding="0"
IsVisible="False"> IsVisible="False">
<Grid <VerticalStackLayout
x:Name="_accountListContainer" x:Name="_accountListContainer"
VerticalOptions="Fill" VerticalOptions="Fill"
HorizontalOptions="Fill" HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent" BackgroundColor="Transparent">
RowDefinitions="Auto, *">
<Frame <Frame
Grid.Row="0"
Padding="0" Padding="0"
HorizontalOptions="Fill" HorizontalOptions="Fill"
VerticalOptions="Start"> VerticalOptions="Start">
@@ -51,13 +49,12 @@
</ListView> </ListView>
</Frame> </Frame>
<BoxView <BoxView
Grid.Row="1"
BackgroundColor="Transparent" BackgroundColor="Transparent"
HorizontalOptions="Fill" HorizontalOptions="Fill"
VerticalOptions="Fill"> VerticalOptions="FillAndExpand">
<BoxView.GestureRecognizers> <BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" /> <TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
</BoxView.GestureRecognizers> </BoxView.GestureRecognizers>
</BoxView> </BoxView>
</Grid> </VerticalStackLayout>
</ContentView> </ContentView>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell" x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
xmlns:core="clr-namespace:Bit.Core" xmlns:core="clr-namespace:Bit.Core"
StyleClass="list-row, list-row-platform" StyleClass="list-row, list-row-platform"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
@@ -13,32 +14,34 @@
RowSpacing="0" RowSpacing="0"
Padding="0,10,0,0" Padding="0,10,0,0"
RowDefinitions="*,*"> RowDefinitions="*,*">
<controls:BaseCipherViewCell.Resources>
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter" /> <u:IconGlyphConverter x:Key="iconGlyphConverter" />
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
</controls:BaseCipherViewCell.Resources> </Grid.Resources>
<controls:CachedImage <controls:IconLabel
x:Name="_iconImage"
Grid.Column="0" Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Grid.RowSpan="2" Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True" BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"
WidthRequest="22" WidthRequest="22"
HeightRequest="22" HeightRequest="22"
Success="Icon_Success"
Error="Icon_Error"
AutomationProperties.IsInAccessibleTree="False" />
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
Grid.RowSpan="2" Grid.RowSpan="2"
HorizontalOptions="Center" IsVisible="{Binding ShowIconImage}"
VerticalOptions="Center" Source="{Binding IconImageSource, Mode=OneTime}"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
<Label <Label
@@ -46,7 +49,7 @@
Grid.Column="1" Grid.Column="1"
Grid.Row="0" Grid.Row="0"
VerticalTextAlignment="Center" VerticalTextAlignment="Center"
VerticalOptions="End" VerticalOptions="Fill"
StyleClass="list-title, list-title-platform" StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" /> Text="{Binding Cipher.Name}" />
@@ -55,7 +58,7 @@
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
VerticalTextAlignment="Center" VerticalTextAlignment="Center"
VerticalOptions="Start" VerticalOptions="Fill"
StyleClass="list-subtitle, list-subtitle-platform" StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" /> Text="{Binding Cipher.SubTitle}" />
@@ -65,14 +68,11 @@
Grid.Column="2" Grid.Column="2"
Grid.RowSpan="2" Grid.RowSpan="2"
HorizontalOptions="Fill" HorizontalOptions="Fill"
WidthRequest="50"
HeightRequest="50"
VerticalOptions="CenterAndExpand" /> VerticalOptions="CenterAndExpand" />
<Label <Label
Text="{Binding TotpSec, Mode=OneWay}" Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}" Style="{DynamicResource textTotp}"
BackgroundColor="Transparent"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Grid.RowSpan="2" Grid.RowSpan="2"
@@ -123,4 +123,4 @@
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"
SemanticProperties.Description="{u:I18n CopyTotp}" /> SemanticProperties.Description="{u:I18n CopyTotp}" />
</controls:BaseCipherViewCell> </controls:ExtendedGrid>

View File

@@ -1,14 +1,10 @@
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
public partial class AuthenticatorViewCell : BaseCipherViewCell public partial class AuthenticatorViewCell : ExtendedGrid
{ {
public AuthenticatorViewCell() public AuthenticatorViewCell()
{ {
InitializeComponent(); InitializeComponent();
} }
protected override CachedImage Icon => _iconImage;
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
} }
} }

View File

@@ -1,62 +0,0 @@
using SkiaSharp;
namespace Bit.App.Controls
{
public class AvatarImageSource : StreamImageSource
{
private readonly string _text;
private readonly string _id;
private readonly string _color;
private readonly AvatarInfo _avatarInfo;
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
}
return base.Equals(obj);
}
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
{
_id = userId;
_text = name;
if (string.IsNullOrWhiteSpace(_text))
{
_text = email;
}
_color = color;
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
_avatarInfo = new AvatarInfo(userId, name, email, color, DeviceInfo.Platform == DevicePlatform.iOS ? 20 : 50);
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
{
var result = Draw();
return Task.FromResult(result);
}
private Stream Draw()
{
using (var img = SKAvatarImageHelper.Draw(_avatarInfo))
{
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return data?.AsStream(true);
}
}
}
}

View File

@@ -1,63 +0,0 @@
using Bit.Core.Utilities;
#nullable enable
namespace Bit.App.Controls
{
public struct AvatarInfo
{
private const string DEFAULT_BACKGROUND_COLOR = "#33ffffff";
public AvatarInfo(string? userId = null, string? name = null, string? email = null, string? color = null, int size = 50)
{
Size = size;
var text = string.IsNullOrWhiteSpace(name) ? email : name;
string? upperCaseText = null;
if (string.IsNullOrEmpty(text))
{
CharsToDraw = "..";
}
else if (text.Length > 1)
{
upperCaseText = text.ToUpper();
CharsToDraw = GetFirstLetters(upperCaseText, 2);
}
else
{
CharsToDraw = upperCaseText = text.ToUpper();
}
BackgroundColor = color ?? CoreHelpers.StringToColor(userId ?? upperCaseText, DEFAULT_BACKGROUND_COLOR);
TextColor = CoreHelpers.TextColorFromBgColor(BackgroundColor);
}
public string CharsToDraw { get; }
public string BackgroundColor { get; }
public string TextColor { get; }
public int Size { get; }
private static string GetFirstLetters(string data, int charCount)
{
var sanitizedData = data.Trim();
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1 && charCount <= 2)
{
var text = string.Empty;
for (var i = 0; i < charCount; i++)
{
text += parts[i][0];
}
return text;
}
if (sanitizedData.Length > 2)
{
return sanitizedData.Substring(0, 2);
}
return sanitizedData;
}
}
}

View File

@@ -1,63 +0,0 @@
using SkiaSharp;
namespace Bit.App.Controls
{
public static class SKAvatarImageHelper
{
public static SKImage Draw(AvatarInfo avatarInfo)
{
using (var bitmap = new SKBitmap(avatarInfo.Size * 2,
avatarInfo.Size * 2,
SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
{
using (var canvas = new SKCanvas(bitmap))
{
canvas.Clear(SKColors.Transparent);
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(avatarInfo.BackgroundColor)
})
{
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
using (var circlePaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(avatarInfo.BackgroundColor)
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
using (var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(avatarInfo.TextColor),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
})
{
var rect = new SKRect();
textPaint.MeasureText(avatarInfo.CharsToDraw, ref rect);
canvas.DrawText(avatarInfo.CharsToDraw, midX, midY + rect.Height / 2, textPaint);
return SKImage.FromBitmap(bitmap);
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,179 @@
using Bit.Core.Utilities;
using SkiaSharp;
namespace Bit.App.Controls
{
public class AvatarImageSource : StreamImageSource
{
private readonly string _text;
private readonly string _id;
private readonly string _color;
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
}
return base.Equals(obj);
}
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
{
_id = userId;
_text = name;
if (string.IsNullOrWhiteSpace(_text))
{
_text = email;
}
_color = color;
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
{
var result = Draw();
return Task.FromResult(result);
}
private Stream Draw()
{
string chars;
string upperCaseText = null;
if (string.IsNullOrEmpty(_text))
{
chars = "..";
}
else if (_text?.Length > 1)
{
upperCaseText = _text.ToUpper();
chars = GetFirstLetters(upperCaseText, 2);
}
else
{
chars = upperCaseText = _text.ToUpper();
}
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
var size = 50;
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
size = 20;
}
using (var bitmap = new SKBitmap(size * 2,
size * 2,
SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
{
using (var canvas = new SKCanvas(bitmap))
{
canvas.Clear(SKColors.Transparent);
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor)
})
{
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
using (var circlePaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor)
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
using (var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
})
{
var rect = new SKRect();
textPaint.MeasureText(chars, ref rect);
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
using (var img = SKImage.FromBitmap(bitmap))
{
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return data?.AsStream(true);
}
}
}
}
}
}
}
private string GetFirstLetters(string data, int charCount)
{
var sanitizedData = data.Trim();
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1 && charCount <= 2)
{
var text = string.Empty;
for (var i = 0; i < charCount; i++)
{
text += parts[i][0];
}
return text;
}
if (sanitizedData.Length > 2)
{
return sanitizedData.Substring(0, 2);
}
return sanitizedData;
}
private Color StringToColor(string str)
{
if (str == null)
{
return Color.FromArgb("#33ffffff");
}
var hash = 0;
for (var i = 0; i < str.Length; i++)
{
hash = str[i] + ((hash << 5) - hash);
}
var color = "#FF";
for (var i = 0; i < 3; i++)
{
var value = (hash >> (i * 8)) & 0xff;
var base16 = "00" + Convert.ToString(value, 16);
color += base16.Substring(base16.Length - 2);
}
return Color.FromArgb(color);
}
}
}

View File

@@ -1,42 +0,0 @@
namespace Bit.App.Controls
{
#if !UT
public class CachedImage : FFImageLoading.Maui.CachedImage
{
}
#else
/// <summary>
/// Given that FFImageLoading package doesn't support net8.0 then for Unit tests projects to build and run correctly
/// we need to not include the reference to FFImageLoading and therefore wrap this class
/// to provide a stub one that does nothing so this project doesn't break and we can run the tests.
/// </summary>
public class CachedImage : View
{
public static readonly BindableProperty SourceProperty = BindableProperty.Create(
nameof(Source), typeof(ImageSource), typeof(CachedImage));
public static readonly BindableProperty AspectProperty = BindableProperty.Create(
nameof(Aspect), typeof(Aspect), typeof(CachedImage));
public bool BitmapOptimizations { get; set; }
public string ErrorPlaceholder { get; set; }
public string LoadingPlaceholder { get; set; }
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Aspect Aspect
{
get { return (Aspect)GetValue(AspectProperty); }
set { SetValue(AspectProperty, value); }
}
public bool IsLoading { get; set; }
public event EventHandler Success;
public event EventHandler Error;
}
#endif
}

View File

@@ -1,111 +0,0 @@
using Bit.App.Pages;
namespace Bit.App.Controls
{
public abstract class BaseCipherViewCell : ExtendedGrid
{
protected virtual CachedImage Icon { get; }
protected virtual IconLabel IconPlaceholder { get; }
// HACK: PM-5896 Fix for Background Crash on iOS
// While loading the cipher icon and the user sent the app to background
// the app was crashing sometimes when the "LoadingPlaceholder" or "ErrorPlaceholder"
// were being accessed, thus locked, and as soon the app got suspended by the OS
// the app would crash because there can't be any lock files by the app when it gets suspended.
// So, the approach has changed to reuse the IconLabel default icon to use it for these placeholders
// as well. In order to do that both icon controls change their visibility dynamically here reacting to
// CachedImage events and binding context changes.
protected override void OnBindingContextChanged()
{
Icon.Source = null;
if (BindingContext is CipherItemViewModel cipherItemVM)
{
Icon.Source = cipherItemVM.IconImageSource;
if (!cipherItemVM.IconImageSuccesfullyLoaded)
{
UpdateIconImages(cipherItemVM.ShowIconImage);
}
}
base.OnBindingContextChanged();
}
private void UpdateIconImages(bool showIcon)
{
MainThread.BeginInvokeOnMainThread(() =>
{
if (!showIcon)
{
Icon.IsVisible = false;
IconPlaceholder.IsVisible = true;
return;
}
IconPlaceholder.IsVisible = Icon.IsLoading;
});
}
#if !UT
public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e)
{
if (BindingContext is CipherItemViewModel cipherItemVM)
{
cipherItemVM.IconImageSuccesfullyLoaded = true;
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = cipherItemVM.ShowIconImage;
IconPlaceholder.IsVisible = !cipherItemVM.ShowIconImage;
});
}
}
public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)
{
if (BindingContext is CipherItemViewModel cipherItemVM)
{
cipherItemVM.IconImageSuccesfullyLoaded = false;
}
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = false;
IconPlaceholder.IsVisible = true;
});
}
#else
private void Icon_Success(object sender, EventArgs e) {}
private void Icon_Error(object sender, EventArgs e) {}
#endif
}
public class StubBaseCipherViewCellSoLinkerDoesntRemoveMethods : BaseCipherViewCell
{
protected override CachedImage Icon => new CachedImage();
protected override IconLabel IconPlaceholder => new IconLabel();
public static void CallThisSoLinkerDoesntRemoveMethods()
{
#if !UT
var stub = new StubBaseCipherViewCellSoLinkerDoesntRemoveMethods();
try
{
stub.Icon_Success(stub, new FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs(new FFImageLoading.Work.ImageInformation(), FFImageLoading.Work.LoadingResult.Disk));
}
catch (Exception)
{
}
try
{
stub.Icon_Error(stub, new FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs(new InvalidOperationException("stub")));
}
catch (Exception)
{
}
#endif
}
}
}

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell" x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
xmlns:core="clr-namespace:Bit.Core" xmlns:core="clr-namespace:Bit.Core"
StyleClass="list-row, list-row-platform" StyleClass="list-row, list-row-platform"
RowSpacing="0" RowSpacing="0"
@@ -29,32 +30,34 @@
<ColumnDefinition Width="60" /> <ColumnDefinition Width="60" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<controls:CachedImage <controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<ff:CachedImage
x:Name="_iconImage" x:Name="_iconImage"
Grid.Column="0" Grid.Column="0"
BitmapOptimizations="True" BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Margin="9" Margin="9"
WidthRequest="22" WidthRequest="22"
HeightRequest="22" HeightRequest="22"
Aspect="AspectFit" Aspect="AspectFit"
Success="Icon_Success" IsVisible="{Binding ShowIconImage}"
Error="Icon_Error" Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherWebsiteIcon" /> AutomationId="CipherWebsiteIcon" />
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7"> <Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -119,4 +122,4 @@
SemanticProperties.Description="{u:I18n Options}" SemanticProperties.Description="{u:I18n Options}"
AutomationId="CipherOptionsButton" /> AutomationId="CipherOptionsButton" />
</controls:BaseCipherViewCell> </controls:ExtendedGrid>

View File

@@ -5,7 +5,7 @@ using Bit.Core.Utilities;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
public partial class CipherViewCell : BaseCipherViewCell public partial class CipherViewCell : ExtendedGrid
{ {
private const int ICON_COLUMN_DEFAULT_WIDTH = 40; private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
private const int ICON_IMAGE_DEFAULT_WIDTH = 22; private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
@@ -23,10 +23,6 @@ namespace Bit.App.Controls
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale; _iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
} }
protected override CachedImage Icon => _iconImage;
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
public ICommand ButtonCommand public ICommand ButtonCommand
{ {
get => GetValue(ButtonCommandProperty) as ICommand; get => GetValue(ButtonCommandProperty) as ICommand;

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<ContentView <ContentView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@@ -17,7 +17,7 @@
LineBreakMode="TailTruncation" /> LineBreakMode="TailTruncation" />
<controls:IconLabel <controls:IconLabel
Text="{Binding Source={x:Static core:BitwardenIcons.ExternalLink}}" Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
TextColor="{DynamicResource TextColor}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="End" HorizontalOptions="End"
VerticalOptions="Center" VerticalOptions="Center"

View File

@@ -1,299 +0,0 @@
#if ANDROID
using System;
using System.Collections.Specialized;
using Android.App;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.Text;
using Android.Text.Style;
using Android.Widget;
using Microsoft.Maui;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using AGravityFlags = Android.Views.GravityFlags;
using ALayoutDirection = Android.Views.LayoutDirection;
using AppCompatAlertDialog = AndroidX.AppCompat.App.AlertDialog;
using AResource = Android.Resource;
using ATextAlignment = Android.Views.TextAlignment;
using ATextDirection = Android.Views.TextDirection;
namespace Bit.Core.Controls.Picker
{
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
// This is an adapted copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.Android.cs
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
{
AppCompatAlertDialog? _dialog;
protected override MauiPicker CreatePlatformView() =>
new MauiPicker(Context);
protected override void ConnectHandler(MauiPicker platformView)
{
platformView.FocusChange += OnFocusChange;
platformView.Click += OnClick;
base.ConnectHandler(platformView);
}
protected override void DisconnectHandler(MauiPicker platformView)
{
platformView.FocusChange -= OnFocusChange;
platformView.Click -= OnClick;
base.DisconnectHandler(platformView);
}
// This is a Android-specific mapping
public static void MapBackground(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateBackground(picker);
}
// TODO Uncomment me on NET8 [Obsolete]
public static void MapReload(IPickerHandler handler, IPicker picker, object? args) => Reload(handler);
internal static void MapItems(IPickerHandler handler, IPicker picker) => Reload(handler);
public static void MapTitle(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitle(picker);
}
public static void MapTitleColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitleColor(picker);
}
public static void MapSelectedIndex(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateSelectedIndex(picker);
}
public static void MapCharacterSpacing(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateCharacterSpacing(picker);
}
public static void MapFont(IPickerHandler handler, IPicker picker)
{
var fontManager = handler.GetRequiredService<IFontManager>();
handler.PlatformView?.UpdateFont(picker, fontManager);
}
public static void MapHorizontalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateHorizontalAlignment(picker.HorizontalTextAlignment);
}
public static void MapTextColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView.UpdateTextColor(picker);
}
public static void MapVerticalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateVerticalAlignment(picker.VerticalTextAlignment);
}
void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventArgs e)
{
if (PlatformView == null)
return;
if (e.HasFocus)
{
if (PlatformView.Clickable)
PlatformView.CallOnClick();
else
OnClick(PlatformView, EventArgs.Empty);
}
else if (_dialog != null)
{
_dialog.Hide();
_dialog = null;
}
}
void OnClick(object? sender, EventArgs e)
{
if (_dialog == null && VirtualView != null)
{
using (var builder = new AppCompatAlertDialog.Builder(Context))
{
if (VirtualView.TitleColor == null)
{
builder.SetTitle(VirtualView.Title ?? string.Empty);
}
else
{
var title = new SpannableString(VirtualView.Title ?? string.Empty);
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
title.SetSpan(new ForegroundColorSpan(VirtualView.TitleColor.ToPlatform()), 0, title.Length(), SpanTypes.ExclusiveExclusive);
#pragma warning restore CA1416
builder.SetTitle(title);
}
string[] items = VirtualView.GetItemsAsArray();
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
if (item == null)
items[i] = String.Empty;
}
builder.SetSingleChoiceItems(items, VirtualView.SelectedIndex, (s, e) =>
{
var selectedIndex = e.Which;
VirtualView.SelectedIndex = selectedIndex;
base.PlatformView?.UpdatePicker(VirtualView);
_dialog.Dismiss();
});
builder.SetNegativeButton(AResource.String.Cancel, (o, args) => { });
_dialog = builder.Create();
}
if (_dialog == null)
return;
_dialog.UpdateFlowDirection(PlatformView);
_dialog.SetCanceledOnTouchOutside(true);
_dialog.DismissEvent += (sender, args) =>
{
_dialog = null;
};
_dialog.Show();
}
}
static void Reload(IPickerHandler handler)
{
handler.PlatformView.UpdatePicker(handler.VirtualView);
}
}
public static class PickerExtensions
{
const AGravityFlags HorizontalGravityMask = AGravityFlags.CenterHorizontal | AGravityFlags.End | AGravityFlags.Start;
internal static void UpdatePicker(this MauiPicker platformPicker, IPicker picker)
{
platformPicker.Hint = picker.Title;
if (picker.SelectedIndex == -1 || picker.SelectedIndex >= picker.GetCount())
platformPicker.Text = null;
else
platformPicker.Text = picker.GetItem(picker.SelectedIndex);
}
internal static void UpdateHorizontalAlignment(this EditText view, TextAlignment alignment, AGravityFlags orMask = AGravityFlags.NoGravity)
{
if (!Rtl.IsSupported)
{
view.Gravity = (view.Gravity & ~HorizontalGravityMask) | alignment.ToHorizontalGravityFlags() | orMask;
}
else
view.TextAlignment = alignment.ToTextAlignment();
}
internal static AGravityFlags ToHorizontalGravityFlags(this TextAlignment alignment)
{
switch (alignment)
{
case TextAlignment.Center:
return AGravityFlags.CenterHorizontal;
case TextAlignment.End:
return AGravityFlags.End;
default:
return AGravityFlags.Start;
}
}
internal static ATextAlignment ToTextAlignment(this TextAlignment alignment)
{
switch (alignment)
{
case TextAlignment.Center:
return ATextAlignment.Center;
case TextAlignment.End:
return ATextAlignment.ViewEnd;
default:
return ATextAlignment.ViewStart;
}
}
internal static void UpdateFlowDirection(this AndroidX.AppCompat.App.AlertDialog alertDialog, MauiPicker platformPicker)
{
var platformLayoutDirection = platformPicker.LayoutDirection;
// Propagate the MauiPicker LayoutDirection to the AlertDialog
var dv = alertDialog.Window?.DecorView;
if (dv is not null)
dv.LayoutDirection = platformLayoutDirection;
var lv = alertDialog?.ListView;
if (lv is not null)
{
lv.LayoutDirection = platformLayoutDirection;
lv.TextDirection = platformLayoutDirection.ToTextDirection();
}
}
internal static ATextDirection ToTextDirection(this ALayoutDirection direction)
{
switch (direction)
{
case ALayoutDirection.Ltr:
return ATextDirection.Ltr;
case ALayoutDirection.Rtl:
return ATextDirection.Rtl;
default:
return ATextDirection.Inherit;
}
}
public static T GetRequiredService<T>(this IElementHandler handler)
where T : notnull
{
var services = handler.GetServiceProvider();
var service = services.GetRequiredService<T>();
return service;
}
public static IServiceProvider GetServiceProvider(this IElementHandler handler)
{
var context = handler.MauiContext ??
throw new InvalidOperationException($"Unable to find the context. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
var services = context?.Services ??
throw new InvalidOperationException($"Unable to find the service provider. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
return services;
}
}
static class Rtl
{
/// <summary>
/// True if /manifest/application@android:supportsRtl="true"
/// </summary>
public static readonly bool IsSupported =
(Android.App.Application.Context?.ApplicationInfo?.Flags & ApplicationInfoFlags.SupportsRtl) != 0;
}
}
#endif

View File

@@ -1,53 +0,0 @@
#if ANDROID
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
namespace Bit.Core.Controls.Picker
{
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
// This is a copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.cs
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>, IPickerHandler
{
public static IPropertyMapper<IPicker, IPickerHandler> Mapper = new PropertyMapper<IPicker, PickerHandler>(ViewMapper)
{
#if __ANDROID__ || WINDOWS
[nameof(IPicker.Background)] = MapBackground,
#endif
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
[nameof(IPicker.Font)] = MapFont,
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
[nameof(IPicker.TextColor)] = MapTextColor,
[nameof(IPicker.Title)] = MapTitle,
[nameof(IPicker.TitleColor)] = MapTitleColor,
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
[nameof(IPicker.Items)] = MapItems,
};
public static CommandMapper<IPicker, IPickerHandler> CommandMapper = new(ViewCommandMapper)
{
};
public PickerHandler() : base(Mapper, CommandMapper)
{
}
public PickerHandler(IPropertyMapper? mapper)
: base(mapper ?? Mapper, CommandMapper)
{
}
public PickerHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
{
}
IPicker IPickerHandler.VirtualView => VirtualView;
Microsoft.Maui.Platform.MauiPicker IPickerHandler.PlatformView => PlatformView;
}
}
#endif

View File

@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<!--When running Unit tests we'll have the custom constant "UT" added, so in this manner we can add the net8.0 target we need for UT --> <TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
<TargetFrameworks Condition="$(CustomConstants.Contains(UT))">net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
<TargetFrameworks Condition="!$(CustomConstants.Contains(UT))">net8.0-android;net8.0-ios</TargetFrameworks>
<RootNamespace>Bit.Core</RootNamespace> <RootNamespace>Bit.Core</RootNamespace>
<UseMaui>true</UseMaui> <UseMaui>true</UseMaui>
@@ -34,7 +32,6 @@
<PackageReference Include="CsvHelper" Version="30.0.1" /> <PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" /> <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="zxcvbn-core" Version="7.0.92" />
<PackageReference Include="MessagePack.MSBuild.Tasks" Version="2.5.124"> <PackageReference Include="MessagePack.MSBuild.Tasks" Version="2.5.124">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -44,16 +41,14 @@
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" /> <PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" /> <PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" /> <PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" /> <PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<!-- HACK: When running Unit Tests we cannot load FFImageLoading because it doesn't support "raw" net8.0 -->
<PackageReference Condition="!$(CustomConstants.Contains(UT))" Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" /> <PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" /> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
@@ -69,6 +64,7 @@
<Folder Include="Resources\Fonts\" /> <Folder Include="Resources\Fonts\" />
<Folder Include="Effects\" /> <Folder Include="Effects\" />
<Folder Include="Resources\Raw\" /> <Folder Include="Resources\Raw\" />
<Folder Include="Pages\" />
<Folder Include="Behaviors\" /> <Folder Include="Behaviors\" />
<Folder Include="Controls\" /> <Folder Include="Controls\" />
<Folder Include="Lists\" /> <Folder Include="Lists\" />
@@ -77,11 +73,6 @@
<Folder Include="Utilities\Automation\" /> <Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" /> <Folder Include="Utilities\Prompts\" />
<Folder Include="Resources\Localization\" /> <Folder Include="Resources\Localization\" />
<Folder Include="Utilities\Fido2\" />
<Folder Include="Controls\Picker\" />
<Folder Include="Controls\Avatar\" />
<Folder Include="Services\UserVerification\" />
<Folder Include="Utilities\WebAuthenticatorMAUI\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MauiImage Include="Resources\Images\dotnet_bot.svg"> <MauiImage Include="Resources\Images\dotnet_bot.svg">
@@ -95,25 +86,10 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput> <LastGenOutput>AppResources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource> </EmbeddedResource>
<Compile Update="Pages\AndroidNavigationRedirectPage.xaml.cs">
<DependentUpon>AndroidNavigationRedirectPage.xaml</DependentUpon>
</Compile>
<Compile Update="Resources\Localization\AppResources.Designer.cs"> <Compile Update="Resources\Localization\AppResources.Designer.cs">
<DependentUpon>AppResources.resx</DependentUpon> <DependentUpon>AppResources.resx</DependentUpon>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<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\" />
</ItemGroup>
</Project> </Project>

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