1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-24 21:33:23 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Matt Portune
c1461ab16b FIDO2 implementation using Google Play Services on Android 2021-08-30 13:38:03 -04:00
245 changed files with 3069 additions and 11920 deletions

View File

@@ -1,81 +0,0 @@
name: Bug Report
description: File a bug report
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce
attributes:
label: Steps To Reproduce
description: How can we reproduce the behavior.
value: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. Click on '...'
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Result
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Result
description: A clear and concise description of what is happening.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots or Videos
description: If applicable, add screenshots and/or a short video to help explain your problem.
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you seeing the problem on?
multiple: true
options:
- Android
- iOS
validations:
required: true
- type: input
id: os-version
attributes:
label: Operating System Version
description: What version of the operating system(s) are you seeing the problem on?
- type: input
id: device
attributes:
label: Device
description: Which device are you seeing the problem on?
placeholder: iPhone 12, Samsung Galaxy S10
- type: input
id: version
attributes:
label: Build Version
description: What version of our software are you running? (go to "Settings" → "About" in the app)
validations:
required: true
- type: checkboxes
id: beta
attributes:
label: Beta
options:
- label: Using a pre-release version of the application.

View File

@@ -1,17 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Report mobile autofill failure
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users!
- name: Feature Requests
url: https://community.bitwarden.com/c/feature-requests/
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
- name: Bitwarden Community Forums
url: https://community.bitwarden.com
about: Please visit the community forums for general community discussion, support and the development roadmap.
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.
- name: Security Issues
url: https://hackerone.com/bitwarden
about: We use HackerOne to manage security disclosures.

13
.github/scripts/android/build.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration
)
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/p:Configuration=$configuration"

View File

@@ -0,0 +1,69 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appPath = $($rootPath + "/src/App/App.csproj");
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
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 $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$xml.Save($androidManifest);
Write-Output "########################################"
Write-Output "##### Uninstall from Android.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$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($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
cd $GITHUB_WORKSPACE
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
https://github.com/bitwarden/mobile/releases/download/$RELEASE_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE

View File

@@ -0,0 +1,22 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystorePlayPath = $($rootPath + "/src/Android/$appKeystorePlayFilename");
$appKeystoreUploadFilename = "app_upload-keystore.jks";
$appKeystoreUploadPath = $($rootPath + "/src/Android/$appKeystoreUploadFilename");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
$appKeystoreFdroidPath = $($rootPath + "/src/Android/$appKeystoreFdroidFilename");
$googleServicesFilename = "google-services.json";
$googleServicesPath = $($rootPath + "/src/Android/$googleServicesFilename");
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystorePlayFilename + ".gpg") -output $($appKeystorePlayPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreUploadFilename + ".gpg") -output $($appKeystoreUploadPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreFdroidFilename + ".gpg") -output $($appKeystoreFdroidPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($googleServicesFilename + ".gpg") -output $($googleServicesPath)"
Invoke-Expression "& `"$decryptSecretPath`" -filename play_creds.json.gpg"

View File

@@ -0,0 +1,9 @@
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$publisherPath = $($rootPath + "/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll");
$credsPath = $($homePath + "/secrets/play_creds.json");
$aabPath = $($rootPath + "/com.x8bit.bitwarden.aab");
$track = "internal";
dotnet $publisherPath $credsPath $aabPath $track

View File

@@ -0,0 +1,16 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 3000 + [int]$env:GITHUB_RUN_NUMBER;
Write-Output "########################################"
Write-Output "##### Setting Version Code $buildNumber"
Write-Output "########################################"
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
$xml=New-Object XML;
$xml.Load($androidManifest);
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", [string]$buildNumber);
$xml.Save($androidManifest);

23
.github/scripts/android/sign-fdroid.ps1 vendored Normal file
View File

@@ -0,0 +1,23 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign FDroid Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreFdroidFilename)" `
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath

42
.github/scripts/android/sign-play.ps1 vendored Normal file
View File

@@ -0,0 +1,42 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystoreUploadFilename = "app_upload-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreUploadFilename)" `
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($rootPath + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystorePlayFilename)" `
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath

29
.github/scripts/decrypt-secret.ps1 vendored Normal file
View File

@@ -0,0 +1,29 @@
param (
[Parameter(Mandatory=$true)]
[string] $filename,
[string] $output
)
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$rootPath = $env:GITHUB_WORKSPACE
$secretInputPath = $rootPath + "/.github/secrets"
$input = $secretInputPath + "/" + $filename
$passphrase = $env:DECRYPT_FILE_PASSWORD
$secretOutputPath = $homePath + "/secrets"
if ([string]::IsNullOrEmpty($output)) {
if ($filename.EndsWith(".gpg")) {
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
} else {
$output = $secretOutputPath + "/" + $filename + ".plaintext"
}
}
if (!(Test-Path -Path $secretOutputPath))
{
New-Item -ItemType Directory -Path $secretOutputPath
}
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input

29
.github/scripts/ios/build.ps1 vendored Normal file
View File

@@ -0,0 +1,29 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration,
[string] $platform = "iPhone",
[switch] $archive
)
$rootPath = $env:GITHUB_WORKSPACE;
$iosPath = $($rootPath + "/src/iOS/iOS.csproj");
if ($archive)
{
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" `
"/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
} else
{
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" "/t:`"Build`""
}

View File

@@ -0,0 +1,9 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden-mobile-key.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename iphone-distribution-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_autofill.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_bitwarden.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_extension.mobileprovision.gpg"

View File

@@ -0,0 +1,5 @@
$rootPath = $env:GITHUB_WORKSPACE;
$ipaPath = "$rootPath/bitwarden-export/Bitwarden.ipa"
xcrun altool --upload-app --type ios --file "$ipaPath" `
--username "$env:APPLE_ID_USERNAME" --password "$env:APPLE_ID_PASSWORD"

13
.github/scripts/ios/export-ipa.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
param (
[Parameter(Mandatory=$true)]
[string] $method
)
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$exportOptionsPath = "$rootPath/.github/resources/export-options-$method.plist";
$archivePath = "$homePath/Library/Developer/Xcode/Archives/*/*.xcarchive";
$exportPath = "$rootPath/bitwarden-export";
xcodebuild -exportArchive -archivePath $archivePath -exportPath $exportPath -exportOptionsPlist $exportOptionsPath

View File

@@ -0,0 +1,26 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 100 + [int]$env:GITHUB_RUN_NUMBER;
$bitwardenInfo = $($rootPath + "/src/iOS/Info.plist");
$extensionInfo = $($rootPath + "/src/iOS.Extension/Info.plist");
$autofillInfo = $($rootPath + "/src/iOS.Autofill/Info.plist");
Write-Output "########################################"
Write-Output "##### Setting CFBundleVersion $buildNumber"
Write-Output "########################################"
function Update-Version($file) {
$xml=New-Object XML;
$xml.Load($file);
Select-Xml -xml $xml -XPath "//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $buildNumber
}
$xml.Save($file);
}
Update-Version $bitwardenInfo
Update-Version $extensionInfo
Update-Version $autofillInfo

13
.github/scripts/ios/setup-keychain.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$mobileKeyPath = $($secretsPath + "/bitwarden-mobile-key.p12");
$distCertPath = $($secretsPath + "/iphone-distribution-cert.p12");
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $mobileKeyPath -k build.keychain -P $env:MOBILE_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security import $distCertPath -k build.keychain -P $env:DIST_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain

21
.github/scripts/ios/setup-profiles.ps1 vendored Normal file
View File

@@ -0,0 +1,21 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$autofillProfilePath = $($secretsPath + "/dist_autofill.mobileprovision");
$bitwardenProfilePath = $($secretsPath + "/dist_bitwarden.mobileprovision");
$extensionProfilePath = $($secretsPath + "/dist_extension.mobileprovision");
$profilesDirPath = "~/Library/MobileDevice/Provisioning Profiles"
if (!(Test-Path -Path $profilesDirPath))
{
New-Item -ItemType Directory -Path $profilesDirPath
}
$autofill_uuid = grep UUID -A1 -a $autofillProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $autofillProfilePath -destination "$profilesDirPath/$autofill_uuid.mobileprovision"
$bitwarden_uuid = grep UUID -A1 -a $bitwardenProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $bitwardenProfilePath -destination "$profilesDirPath/$bitwarden_uuid.mobileprovision"
$extension_uuid = grep UUID -A1 -a $extensionProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $extensionProfilePath -destination "$profilesDirPath/$extension_uuid.mobileprovision"

View File

@@ -1,4 +1,3 @@
---
name: Build
on:
@@ -6,16 +5,20 @@ on:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
release:
types:
- published
jobs:
cloc:
name: CLOC
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up CLOC
- name: Set up cloc
run: |
sudo apt-get update
sudo apt-get -y install cloc
@@ -23,41 +26,12 @@ jobs:
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
release_branch_exists: ${{ steps.branch-check.outputs.release_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=rc_branch_exists::1"
else
echo "::set-output name=rc_branch_exists::0"
fi
if [[ $(git ls-remote --heads origin release) ]]; then
echo "::set-output name=release_branch_exists::1"
else
echo "::set-output name=release_branch_exists::0"
fi
shell: bash
android:
name: Android
runs-on: windows-2019
needs: setup
runs-on: windows-latest
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Print environment
run: |
@@ -66,294 +40,177 @@ jobs:
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Decrypt secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./src/Android/Properties/AndroidManifest.xml
shell: bash
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/increment-version.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Run Core tests
- name: Run Core Tests
run: dotnet test test/Core.Test/Core.Test.csproj
- name: Build Play Store publisher
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Build for Play Store
run: |
$configuration = "Release";
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
run: ./.github/scripts/android/build.ps1 -configuration Release
shell: pwsh
- name: Sign for Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-play.ps1
shell: pwsh
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_upload-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_play-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh
- name: Upload Play Store .aab artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
if-no-files-found: error
- name: Upload Play Store .apk artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
if-no-files-found: error
- name: Deploy to Play Store
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.release_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.release_branch_exists == 0)
|| github.ref == 'refs/heads/release'
run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
TRACK="internal"
dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
shell: bash
f-droid:
name: F-Droid Build
runs-on: windows-2019
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
- name: Print environment
run: |
nuget help | grep Version
msbuild -version
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./src/Android/Properties/AndroidManifest.xml
shell: bash
- name: Clean for F-Droid
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
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 $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$xml.Save($androidManifest);
Write-Output "########################################"
Write-Output "##### Uninstall from Android.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$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($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
run: ./.github/scripts/android/clean-fdroid.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Build for F-Droid
run: |
$configuration = "FDroid";
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
run: ./.github/scripts/android/build.ps1 -configuration FDroid
shell: pwsh
- name: Sign for F-Droid
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-fdroid.ps1
shell: pwsh
env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
run: |
Write-Output "########################################"
Write-Output "##### Sign FDroid Configuration"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" `
"/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks")" `
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
if-no-files-found: error
- name: Deploy to Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/deploy-play.ps1
shell: pwsh
- name: Upload release assets
if: github.event_name == 'release'
run: |
hub release edit `
-a ./com.x8bit.bitwarden.aab `
-a ./com.x8bit.bitwarden.apk `
-a ./com.x8bit.bitwarden-fdroid.apk `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
android-ubuntu:
runs-on: ubuntu-latest
needs: android
steps:
- name: Set up Node
if: github.event_name == 'release'
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '10.x'
- name: Set up F-Droid server
if: github.event_name == 'release'
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- name: Set up git credentials
if: github.event_name == 'release'
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
- name: Print environment
if: github.event_name == 'release'
run: |
node --version
npm --version
git --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
if: github.event_name == 'release'
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install Node dependencies
if: github.event_name == 'release'
run: npm install
- name: Decrypt secrets
if: github.event_name == 'release'
run: |
./.github/scripts/decrypt-secret.ps1 -filename store_fdroid-keystore.jks.gpg `
-output ./store/fdroid/keystore.jks
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Compile for F-Droid Store
if: github.event_name == 'release'
run: |
sudo chmod +x ./.github/scripts/android/compile-fdroid.sh
./.github/scripts/android/compile-fdroid.sh
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Deploy to gh-pages
if: github.event_name == 'release'
run: npm run deploy
ios:
name: Apple iOS
runs-on: macos-11
needs: setup
runs-on: macos-latest
steps:
- name: Print environment
run: |
@@ -362,125 +219,77 @@ jobs:
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Decrypt secrets
run: ./.github/scripts/ios/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/increment-version.ps1
shell: pwsh
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>/' ./src/iOS/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
shell: bash
- name: Set up Keychain
- name: Set up keychain
run: ./.github/scripts/ios/setup-keychain.ps1
shell: pwsh
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
-T /usr/bin/codesign -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
shell: bash
- name: Set up provisioning profiles
run: |
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
shell: bash
run: ./.github/scripts/ios/setup-profiles.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Archive Build for App Store
run: |
$configuration = "AppStore";
$platform = "iPhone";
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone -archive
shell: pwsh
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
- name: Build for App Store
if: github.ref != 'refs/heads/master'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone
shell: pwsh
- name: Export .ipa for App Store
run: |
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
EXPORT_PATH="./bitwarden-export"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
shell: bash
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/export-ipa.ps1 -method app-store
shell: pwsh
- name: Upload App Store .ipa artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: Bitwarden.ipa
path: ./bitwarden-export/Bitwarden.ipa
if-no-files-found: error
- name: Deploy to App Store
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.release_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.release_branch_exists == 0)
|| github.ref == 'refs/heads/release'
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/deploy-app-store.ps1
shell: pwsh
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Upload release assets
if: github.event_name == 'release'
run: |
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
hub release edit `
-a ./bitwarden-export/Bitwarden.ipa `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}

View File

@@ -1,17 +1,16 @@
---
name: Crowdin Sync
on:
workflow_dispatch:
inputs: {}
# schedule:
# - cron: '0 0 * * *'
#schedule:
# - cron: '0 0 * * *'
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-20.04
env:
env:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo

View File

@@ -1,153 +1,21 @@
---
name: Release
on:
workflow_dispatch:
jobs:
release:
name: Create Release
runs-on: ubuntu-20.04
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/release" ]]; then
echo "==================================="
echo "[!] Can only release from the 'release' branch"
echo "==================================="
exit 1
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
cloc:
runs-on: ubuntu-latest
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -n -e '/android:versionName/ s/.*\= *//p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
echo "::set-output name=mobile_version::${ver}"
shell: bash
- name: Check to make sure Mobile release version has been bumped
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: release
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden.ipa/Bitwarden.ipa"
commit: ${{ github.sha }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Test Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
f-droid:
name: F-Droid Release
runs-on: ubuntu-20.04
needs: release
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Download F-Droid .apk artifact
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: release
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0
with:
node-version: '10.x'
- name: Set up F-Droid server
- name: Set up cloc
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
sudo apt-get update
sudo apt-get -y install cloc
- name: Set up Git credentials
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
- name: Print environment
run: |
node --version
npm --version
git --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Install Node dependencies
run: npm install
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
- name: Compile for F-Droid Store
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
run: |
cd $GITHUB_WORKSPACE
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk ./repo/
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
run: npm run deploy
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML

53
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,53 @@
<!-- Comment:
Please do not submit feature requests. The [Community Forums][1] has a
section for submitting, voting for, and discussing product feature requests.
[1]: https://community.bitwarden.com
-->
## Describe the Bug
<!-- Comment:
A clear and concise description of what the bug is.
-->
## Steps To Reproduce
<!-- Comment:
How can we reproduce the behavior:
-->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. Click on '...'
## Expected Result
<!-- Comment:
A clear and concise description of what you expected to happen.
-->
## Actual Result
<!-- Comment:
A clear and concise description of what is happening.
-->
## Screenshots or Videos
<!-- Comment:
If applicable, add screenshots and/or a short video to help explain your problem.
-->
## Environment
- Device: [e.g. iPhone6]
- Operating system: [e.g. iOS 8.1]
- Build Version (go to "Settings" → "About" in the app): [e.g. 2.3.0 (2221)]
- Is this a Beta release? [Y/N]
## Additional Context
<!-- Comment:
Add any other context about the problem here.
-->

View File

@@ -8,7 +8,7 @@
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="300" height="533" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="533" />
# Build/Run

View File

@@ -9,7 +9,7 @@ using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility
{
[Activity(Theme = "@style/BaseTheme", WindowSoftInputMode = SoftInput.StateHidden)]
[Activity(Theme = "@style/LightTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
public class AccessibilityActivity : Activity
{
private DateTime? _lastLaunch = null;

View File

@@ -76,11 +76,13 @@
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.10</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.9" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.11" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.10" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.5.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat.AppCompatResources" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.6" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.8" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.7" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.4" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
@@ -88,10 +90,11 @@
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.3.0.1" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Fido" Version="118.1.0" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>117.0.1</Version>
<Version>117.0.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -111,7 +114,10 @@
<Compile Include="Autofill\SavedItem.cs" />
<Compile Include="Effects\FabShadowEffect.cs" />
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Fido2System\Fido2BuilderObject.cs" />
<Compile Include="Fido2System\Fido2Service.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
@@ -121,8 +127,6 @@
<Compile Include="Renderers\ExtendedDatePickerRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\ExtendedStackLayoutRenderer.cs" />
<Compile Include="Renderers\ExtendedStepperRenderer.cs" />
<Compile Include="Renderers\CustomSwitchRenderer.cs" />
<Compile Include="Renderers\ExtendedTimePickerRenderer.cs" />
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
@@ -145,9 +149,7 @@
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />
@@ -174,6 +176,8 @@
<AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\info.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_dark.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_nord.xml" />
<AndroidResource Include="Resources\drawable\lock.xml" />
<AndroidResource Include="Resources\drawable\login.xml" />
<AndroidResource Include="Resources\drawable\logo.xml" />
@@ -186,7 +190,6 @@
<AndroidResource Include="Resources\drawable\shield.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen_dark.xml" />
<AndroidResource Include="Resources\drawable\switch_thumb.xml" />
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />

View File

@@ -35,7 +35,6 @@ namespace Bit.Droid.Autofill
public static HashSet<string> TrustedBrowsers = new HashSet<string>
{
"com.duckduckgo.mobile.android",
"com.google.android.googlequicksearchbox",
"org.mozilla.focus",
"org.mozilla.klar",
};

View File

@@ -94,12 +94,24 @@ namespace Bit.Droid.Autofill
_policyService ??= ServiceContainer.Resolve<IPolicyService>("policyService");
var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
if (personalOwnershipPolicyApplies)
var personalOwnershipPolicies = await _policyService.GetAll(PolicyType.PersonalOwnership);
if (personalOwnershipPolicies != null)
{
return;
_userService ??= ServiceContainer.Resolve<IUserService>("userService");
foreach (var policy in personalOwnershipPolicies)
{
if (policy.Enabled)
{
var org = await _userService.GetOrganizationAsync(policy.OrganizationId);
if (org != null && org.Enabled && org.UsePolicies && !org.canManagePolicies
&& org.Status == OrganizationUserStatusType.Confirmed)
{
return;
}
}
}
}
var parser = new Parser(structure, ApplicationContext);
parser.Parse();

View File

@@ -1,6 +1,6 @@
using Android.Graphics.Drawables;
using Bit.App.Utilities;
using Bit.Droid.Effects;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -14,7 +14,7 @@ namespace Bit.Droid.Effects
if (Control is Android.Widget.Button button)
{
var gd = new GradientDrawable();
gd.SetColor(ThemeHelpers.FabColor);
gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
gd.SetCornerRadius(100);
button.SetBackground(gd);

View File

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

View File

@@ -0,0 +1,106 @@
#if !FDROID
using System.Collections.Generic;
using Android.Gms.Fido.Common;
using Android.Gms.Fido.Fido2.Api.Common;
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Java.Lang;
using Newtonsoft.Json.Linq;
namespace Bit.Droid.Fido2System
{
class Fido2BuilderObject
{
public static PublicKeyCredentialRequestOptions ParsePublicKeyCredentialRequestOptions(
Fido2AuthenticationChallengeResponse data)
{
if (data == null)
{
return null;
}
var builder = new PublicKeyCredentialRequestOptions.Builder();
if (!string.IsNullOrEmpty(data.Challenge))
{
builder.SetChallenge(CoreHelpers.Base64UrlDecode(data.Challenge));
}
if (data.AllowCredentials != null && data.AllowCredentials.Count > 0)
{
builder.SetAllowList(ParseCredentialDescriptors(data.AllowCredentials));
}
if (!string.IsNullOrEmpty(data.RpId))
{
builder.SetRpId(data.RpId);
}
if (data.Timeout > 0)
{
builder.SetTimeoutSeconds((Double)(data.Timeout / 1000));
}
if (data.Extensions != null)
{
builder.SetAuthenticationExtensions(ParseExtensions((JObject)data.Extensions));
}
return builder.Build();
}
private static List<PublicKeyCredentialDescriptor> ParseCredentialDescriptors(
List<Fido2CredentialDescriptor> listData)
{
if (listData == null || listData.Count == 0)
{
return new List<PublicKeyCredentialDescriptor>();
}
var credentials = new List<PublicKeyCredentialDescriptor>();
foreach (var data in listData)
{
string id = null;
string type = null;
var transports = new List<Transport>();
if (!string.IsNullOrEmpty(data.Id))
{
id = data.Id;
}
if (!string.IsNullOrEmpty(data.Type))
{
type = data.Type;
}
if (data.Transports != null && data.Transports.Count > 0)
{
foreach (var transport in data.Transports)
{
transports.Add(Transport.FromString(transport));
}
}
credentials.Add(new PublicKeyCredentialDescriptor(type, CoreHelpers.Base64UrlDecode(id), transports));
}
return credentials;
}
private static AuthenticationExtensions ParseExtensions(JObject extensions)
{
var builder = new AuthenticationExtensions.Builder();
if (extensions.ContainsKey("appid"))
{
var appId = new FidoAppIdExtension((string)extensions.GetValue("appid"));
builder.SetFido2Extension(appId);
}
if (extensions.ContainsKey("uvm"))
{
var uvm = new UserVerificationMethodExtension((bool)extensions.GetValue("uvm"));
builder.SetUserVerificationMethodExtension(uvm);
}
return builder.Build();
}
}
}
#endif

View File

@@ -0,0 +1,249 @@
#if !FDROID
using Android.App;
using Android.Content;
using Android.Gms.Fido;
using Android.Gms.Fido.Fido2;
using Android.Gms.Fido.Fido2.Api.Common;
using Android.Gms.Tasks;
using Android.Util;
using AndroidX.AppCompat.App;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Java.Lang;
using Newtonsoft.Json;
using Xamarin.Forms;
using Enum = System.Enum;
namespace Bit.Droid.Fido2System
{
public class Fido2Service
{
public static readonly string _tag_log = "Fido2Service";
public static Fido2Service INSTANCE = new Fido2Service();
private readonly MobileI18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private AppCompatActivity _activity;
private Fido2ApiClient _fido2ApiClient;
private Fido2CodesTypes _fido2CodesType;
public Fido2Service()
{
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
}
public void Start(AppCompatActivity activity)
{
_activity = activity;
_fido2ApiClient = Fido.GetFido2ApiClient(_activity);
}
public void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (resultCode == Result.Ok && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
switch ((Fido2CodesTypes)requestCode)
{
case Fido2CodesTypes.RequestSignInUser:
var errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra);
if (errorExtra != null)
{
HandleErrorCode(errorExtra);
}
else
{
if (data != null)
{
SignInUserResponse(data);
}
}
break;
// TODO: Key registration, should we ever choose to implement client-side
/*case Fido2CodesTypes.RequestRegisterNewKey:
errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra);
if (errorExtra != null)
{
HandleErrorCode(errorExtra);
}
else
{
if (data != null)
{
// begin registration flow
}
}
break;*/
}
}
else if (resultCode == Result.Canceled && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
Log.Info(_tag_log, "cancelled");
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2AbortError"),
_i18nService.T("Fido2Title"));
}
}
public void OnSuccess(Object result)
{
if (result != null && Enum.IsDefined(typeof(Fido2CodesTypes), _fido2CodesType))
{
try
{
_activity.StartIntentSenderForResult(((PendingIntent)result).IntentSender, (int)_fido2CodesType,
null, 0, 0, 0);
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.Message);
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
}
}
public void OnFailure(Exception e)
{
Log.Error(_tag_log, e.Message ?? "OnFailure: No error message returned");
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
public void OnComplete(Task task)
{
Log.Debug(_tag_log, "OnComplete");
}
public async System.Threading.Tasks.Task SignInUserRequestAsync(string dataJson)
{
try
{
var dataObject = JsonConvert.DeserializeObject<Fido2AuthenticationChallengeResponse>(dataJson);
_fido2CodesType = Fido2CodesTypes.RequestSignInUser;
var options = Fido2BuilderObject.ParsePublicKeyCredentialRequestOptions(dataObject);
var task = _fido2ApiClient.GetSignPendingIntent(options);
task.AddOnSuccessListener((IOnSuccessListener)_activity)
.AddOnFailureListener((IOnFailureListener)_activity)
.AddOnCompleteListener((IOnCompleteListener)_activity);
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.StackTrace);
await _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
finally
{
Log.Info(_tag_log, "SignInUserRequest() -> finally()");
}
}
private void SignInUserResponse(Intent data)
{
try
{
var response =
AuthenticatorAssertionResponse.DeserializeFromBytes(
data.GetByteArrayExtra(Fido.Fido2KeyResponseExtra));
var responseJson = JsonConvert.SerializeObject(new Fido2AuthenticationChallengeRequest
{
Id = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()),
RawId = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()),
Type = "public-key",
Response = new Fido2AssertionResponse
{
AuthenticatorData = CoreHelpers.Base64UrlEncode(response.GetAuthenticatorData()),
ClientDataJson = CoreHelpers.Base64UrlEncode(response.GetClientDataJSON()),
Signature = CoreHelpers.Base64UrlEncode(response.GetSignature()),
UserHandle = (response.GetUserHandle() != null
? CoreHelpers.Base64UrlEncode(response.GetUserHandle()) : null),
},
Extensions = null
}
);
Device.BeginInvokeOnMainThread(() => ((MainActivity)_activity).Fido2Submission(responseJson));
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.Message);
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
finally
{
Log.Info(_tag_log, "SignInUserResponse() -> finally()");
}
}
public void HandleErrorCode(byte[] errorExtra)
{
var error = AuthenticatorErrorResponse.DeserializeFromBytes(errorExtra);
if (error.ErrorMessage.Length > 0)
{
Log.Info(_tag_log, error.ErrorMessage);
}
string message = "";
if (error.ErrorCode == ErrorCode.AbortErr)
{
message = "Fido2AbortError";
}
else if (error.ErrorCode == ErrorCode.TimeoutErr)
{
message = "Fido2TimeoutError";
}
else if (error.ErrorCode == ErrorCode.AttestationNotPrivateErr)
{
message = "Fido2PrivacyError";
}
else if (error.ErrorCode == ErrorCode.ConstraintErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.DataErr)
{
message = "Fido2ServerDataFail";
}
else if (error.ErrorCode == ErrorCode.EncodingErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.InvalidStateErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.NetworkErr)
{
message = "Fido2NetworkFail";
}
else if (error.ErrorCode == ErrorCode.NotAllowedErr)
{
message = "Fido2NoPermission";
}
else if (error.ErrorCode == ErrorCode.NotSupportedErr)
{
message = "Fido2NotSupportedError";
}
else if (error.ErrorCode == ErrorCode.SecurityErr)
{
message = "Fido2SecurityError";
}
else if (error.ErrorCode == ErrorCode.UnknownErr)
{
message = "Fido2SomethingWentWrong";
}
else
{
message = "Fido2SomethingWentWrong";
}
_platformUtilsService.ShowDialogAsync(_i18nService.T(message), _i18nService.T("Fido2Title"));
}
}
}
#endif

View File

@@ -9,15 +9,20 @@ using Bit.Core.Utilities;
using Bit.Core.Abstractions;
using System.IO;
using System;
using System.Collections.Generic;
using Android.Content;
using Bit.Droid.Utilities;
using Bit.Droid.Receivers;
using Bit.App.Models;
using Bit.Core.Enums;
using Android.Nfc;
using System.Threading.Tasks;
using AndroidX.Core.Content;
using Bit.App.Utilities;
using System.Threading.Tasks;
using Android.Util;
using AndroidX.Core.Content;
#if !FDROID
using Bit.Droid.Fido2System;
#endif
using ZXing.Net.Mobile.Android;
namespace Bit.Droid
@@ -30,7 +35,7 @@ namespace Bit.Droid
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation | ConfigChanges.UiMode)]
ConfigChanges.Navigation)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
@@ -42,7 +47,10 @@ namespace Bit.Droid
@"text/*"
})]
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity,
Android.Gms.Tasks.IOnSuccessListener,
Android.Gms.Tasks.IOnCompleteListener,
Android.Gms.Tasks.IOnFailureListener
{
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
@@ -57,6 +65,7 @@ namespace Bit.Droid
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
private Java.Util.Regex.Pattern _otpPattern =
Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
private string _fidoDataJson;
protected override void OnCreate(Bundle savedInstanceState)
{
@@ -81,6 +90,7 @@ namespace Bit.Droid
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
UpdateTheme(ThemeManager.GetTheme(true));
base.OnCreate(savedInstanceState);
if (!CoreHelpers.InDebugMode())
{
@@ -90,6 +100,7 @@ namespace Bit.Droid
#if !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterTask = appCenterHelper.InitAsync();
Fido2Service.INSTANCE.Start(this);
#endif
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
@@ -111,13 +122,21 @@ namespace Bit.Droid
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
}
else if (message.Command == "listenFido2")
{
ListenFido2((Dictionary<string, object>)message.Data);
}
else if (message.Command == "listenFido2TryAgain")
{
ListenFido2();
}
else if (message.Command == "listenYubiKeyOTP")
{
ListenYubiKey((bool)message.Data);
}
else if (message.Command == "updatedTheme")
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => AppearanceAdjustments());
RestartApp();
}
else if (message.Command == "exit")
{
@@ -140,7 +159,6 @@ namespace Bit.Droid
{
base.OnResume();
Xamarin.Essentials.Platform.OnResume();
AppearanceAdjustments();
if (_deviceActionService.SupportsNfc())
{
try
@@ -260,6 +278,34 @@ namespace Bit.Droid
return;
}
}
else if (resultCode == Result.Ok &&
Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
#if !FDROID
Fido2Service.INSTANCE.OnActivityResult(requestCode, resultCode, data);
#endif
}
}
public void OnSuccess(Java.Lang.Object result)
{
#if !FDROID
Fido2Service.INSTANCE.OnSuccess(result);
#endif
}
public void OnComplete(Android.Gms.Tasks.Task task)
{
#if !FDROID
Fido2Service.INSTANCE.OnComplete(task);
#endif
}
public void OnFailure(Java.Lang.Exception e)
{
#if !FDROID
Fido2Service.INSTANCE.OnFailure(e);
#endif
}
protected override void OnDestroy()
@@ -268,6 +314,41 @@ namespace Bit.Droid
_broadcasterService.Unsubscribe(_activityKey);
}
private void ListenFido2(Dictionary<string, object> data = null)
{
if (!_deviceActionService.SupportsFido2())
{
return;
}
#if !FDROID
RunOnUiThread(async () =>
{
try
{
if (data != null)
{
_fidoDataJson = Newtonsoft.Json.JsonConvert.SerializeObject(data);
await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson);
}
else
{
await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson);
}
}
catch (Exception e)
{
Log.Error(Fido2Service._tag_log, e.Message);
}
});
#endif
}
public void Fido2Submission(string token)
{
_messagingService.Send("gotFido2Token", token);
}
private void ListenYubiKey(bool listen)
{
if (!_deviceActionService.SupportsNfc())
@@ -382,11 +463,45 @@ namespace Bit.Droid
}
}
private void AppearanceAdjustments()
private void UpdateTheme(string theme)
{
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled());
if (theme == "light")
{
SetTheme(Resource.Style.LightTheme);
}
else if (theme == "dark")
{
SetTheme(Resource.Style.DarkTheme);
}
else if (theme == "black")
{
SetTheme(Resource.Style.BlackTheme);
}
else if (theme == "nord")
{
SetTheme(Resource.Style.NordTheme);
}
else
{
if (_deviceActionService.UsingDarkTheme())
{
SetTheme(Resource.Style.DarkTheme);
}
else
{
SetTheme(Resource.Style.LightTheme);
}
}
}
private void RestartApp()
{
var intent = new Intent(this, typeof(MainActivity));
var pendingIntent = PendingIntent.GetActivity(this, 5923650, intent, PendingIntentFlags.CancelCurrent);
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + 500;
alarmManager.Set(AlarmType.Rtc, triggerMs, pendingIntent);
Java.Lang.JavaSystem.Exit(0);
}
private void ExitApp()

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.14.2"
android:versionName="2.12.0"
android:installLocation="internalOnly"
package="com.x8bit.bitwarden">

View File

@@ -1,9 +1,6 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Content;
using Android.Views.InputMethods;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -28,7 +25,6 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
@@ -37,33 +33,5 @@ namespace Bit.Droid.Renderers
(ImeAction)ImeFlags.NoExtractUi;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -1,12 +1,10 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Text;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -22,7 +20,6 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
@@ -71,28 +68,6 @@ namespace Bit.Droid.Renderers
}
}
}
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -1,8 +1,5 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Content;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -18,40 +15,11 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Picker.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -1,66 +0,0 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Switch), typeof(CustomSwitchRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
{
base.OnElementChanged(e);
UpdateColors();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Switch.OnColorProperty.PropertyName)
{
UpdateColors();
}
}
private void UpdateColors()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
// Android 5.x doesn't support ThumbTintList, and using SwitchCompat on every version after 5.x
// doesn't apply tinting the way we want. Let 5.x to do its own thing here.
return;
}
if (Control != null)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb)
{
Control.ThumbDrawable = thumb;
}
var thumbStates = new[]
{
new[] { Android.Resource.Attribute.StateChecked }, // checked
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
};
var thumbColors = new int[]
{
ThemeHelpers.SwitchOnColor,
ThemeHelpers.SwitchThumbColor
};
Control.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
}
}
}
}

View File

@@ -49,11 +49,11 @@ namespace Bit.Droid.Renderers
return null;
}
public void OnNavigationItemReselected(IMenuItem item)
async void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
await _page.CurrentPage.Navigation.PopToRootAsync();
}
}
}

View File

@@ -1,5 +1,6 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -9,6 +10,8 @@ namespace Bit.Droid.Renderers
{
public class ExtendedGridRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedGridRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
@@ -16,8 +19,25 @@ namespace Bit.Droid.Renderers
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(Resource.Drawable.list_item_bg);
SetBackgroundResource(GetBgResId());
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -1,5 +1,4 @@
using System.ComponentModel;
using Android.Content;
using Android.Content;
using Android.Graphics.Drawables;
using AndroidX.Core.Content.Resources;
using Bit.App.Controls;
@@ -19,21 +18,6 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
UpdateColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedSlider.ThumbBorderColorProperty.PropertyName)
{
UpdateColor();
}
}
private void UpdateColor()
{
if (Control != null && Element is ExtendedSlider view)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);

View File

@@ -1,5 +1,6 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -9,6 +10,8 @@ namespace Bit.Droid.Renderers
{
public class ExtendedStackLayoutRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedStackLayoutRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
@@ -16,8 +19,25 @@ namespace Bit.Droid.Renderers
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(Resource.Drawable.list_item_bg);
SetBackgroundResource(GetBgResId());
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -1,72 +0,0 @@
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedStepper), typeof(ExtendedStepperRenderer))]
namespace Bit.Droid.Renderers
{
public class ExtendedStepperRenderer : StepperRenderer
{
public ExtendedStepperRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
{
base.OnElementChanged(e);
UpdateBgColor();
UpdateFgColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedStepper.StepperBackgroundColorProperty.PropertyName)
{
UpdateBgColor();
}
else if (e.PropertyName == ExtendedStepper.StepperForegroundColorProperty.PropertyName)
{
UpdateFgColor();
}
}
private void UpdateBgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
Control.GetChildAt(1)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
}
else
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
Control.GetChildAt(1)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
}
private void UpdateFgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
var btn0 = Control.GetChildAt(0) as Android.Widget.Button;
btn0?.SetTextColor(view.StepperForegroundColor.ToAndroid());
var btn1 = Control.GetChildAt(1) as Android.Widget.Button;
btn1?.SetTextColor(view.StepperForegroundColor.ToAndroid());
}
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using Android.Content;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(SelectableLabel), typeof(SelectableLabelRenderer))]
namespace Bit.Droid.Renderers
{
public class SelectableLabelRenderer : LabelRenderer
{
public SelectableLabelRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetTextIsSelectable(true);
}
}
}
}

View File

@@ -6,7 +6,7 @@ TODO: When API 23 becomes our new minimum, replace 'splash_screen.xml' in 'drawa
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/white"/>
<color android:color="@color/lightgray"/>
</item>
<item
android:drawable="@drawable/logo"

View File

@@ -1,5 +1,5 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#8E8E93">
android:color="@color/itemPressed">
<item
android:id="@android:id/mask"

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/dark_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/nord_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/white"/>
<color android:color="@color/lightgray"/>
</item>
<item>
<bitmap android:src="@drawable/logo_legacy" android:tileMode="disabled" android:gravity="center"/>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/white"/>
<size android:width="20dp" android:height="20dp"/>
</shape>

View File

@@ -1,26 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!-- Launch theme (for auto dark/light based on system) -->
<style name="LaunchTheme" parent="BaseTheme">
<style name="LaunchTheme" parent="DarkTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="BaseTheme" parent="Theme.AppCompat">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
<item name="colorAccent">@color/dark_primary</item>
<item name="colorControlNormal">@color/dark_border</item>
<item name="android:navigationBarColor">@color/dark_navigationBarBackground</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
<item name="buttonStyle">@style/ButtonStyle</item>
</style>
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textAllCaps">false</item>
</style>
</resources>

View File

@@ -1,19 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Light System -->
<!-- Light theme -->
<color name="colorPrimary">#175DDC</color>
<color name="colorPrimaryDark">#1A3B66</color>
<color name="primary">#175DDC</color>
<color name="notificationBar">#1452BC</color>
<color name="border">#dddddd</color>
<color name="itemPressed">#bbbbbb</color>
<!-- Dark System -->
<!-- Dark theme -->
<color name="dark_primary">#52bdfb</color>
<color name="dark_notificationBar">#191919</color>
<color name="dark_border">#666666</color>
<color name="dark_border">#191919</color>
<color name="dark_navigationBarBackground">#191919</color>
<!-- Black theme -->
<color name="black_border">#282828</color>
<!-- Nord theme -->
<color name="nord_background">#3b4252</color>
<color name="nord_text">#e5e9f0</color>
<color name="nord_primary">#81a1c1</color>
<color name="nord_actionBar">#2e3440</color>
<color name="nord_actionBarText">#e5e9f0</color>
<color name="nord_notificationBar">#20242D</color>
<color name="nord_dialogBackground">#3b4252</color>
<color name="nord_border">#2e3440</color>
<color name="nord_popupBackground">#4c566a</color>
<color name="nord_popupText">#e5e9f0</color>
<color name="nord_buttonBackground">#4c566a</color>
<color name="nord_navigationBarBackground">#20242D</color>
<!-- Other -->
<color name="launcher_background">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="darkgray">#333333</color>

View File

@@ -1,12 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Launch theme (for auto dark/light based on system) -->
<style name="LaunchTheme" parent="BaseTheme">
<style name="LaunchTheme" parent="LightTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Light theme -->
<style name="LightTheme" parent="LightTheme.Base">
</style>
<style name="LightTheme.Splash" parent="LightTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="LightTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimary">@color/primary</item>
@@ -18,10 +27,70 @@
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="buttonStyle">@style/ButtonStyle</item>
</style>
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:textAllCaps">false</item>
</style>
<!-- Dark theme -->
<style name="DarkTheme" parent="DarkTheme.Base">
</style>
<style name="DarkTheme.Splash" parent="DarkTheme.Base">
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="DarkTheme.Base" parent="Theme.AppCompat">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
<item name="colorAccent">@color/dark_primary</item>
<item name="colorControlNormal">@color/dark_border</item>
<item name="android:navigationBarColor">@color/dark_navigationBarBackground</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
<item name="android:textCursorDrawable">@null</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
</style>
<!-- Black theme -->
<style name="BlackTheme" parent="BlackTheme.Base">
</style>
<style name="BlackTheme.Splash" parent="DarkTheme.Splash">
</style>
<style name="BlackTheme.Base" parent="DarkTheme.Base">
<item name="android:windowBackground">@android:color/black</item>
<item name="colorPrimary">@android:color/black</item>
<item name="colorPrimaryDark">@android:color/black</item>
<item name="colorControlNormal">@color/black_border</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<!-- Nord theme -->
<style name="NordTheme" parent="NordTheme.Base">
</style>
<style name="NordTheme.Splash" parent="DarkTheme.Splash">
</style>
<style name="NordTheme.Base" parent="DarkTheme.Base">
<item name="android:windowBackground">@color/nord_background</item>
<item name="android:actionMenuTextColor">@color/nord_actionBarText</item>
<item name="android:textColor">@color/nord_text</item>
<item name="colorAccent">@color/nord_primary</item>
<item name="colorPrimary">@color/nord_actionBar</item>
<item name="colorPrimaryDark">@color/nord_notificationBar</item>
<item name="colorControlNormal">@color/nord_border</item>
<item name="android:navigationBarColor">@color/nord_navigationBarBackground</item>
<item name="colorBackgroundFloating">@color/nord_dialogBackground</item>
<item name="android:colorBackgroundFloating" tools:targetApi="23">@color/nord_dialogBackground</item>
<item name="popupTheme">@style/NordTheme.Popup</item>
<item name="colorButtonNormal">@color/nord_buttonBackground</item>
<item name="android:colorButtonNormal">@color/nord_buttonBackground</item>
</style>
<style name="NordTheme.Popup" parent="ThemeOverlay.AppCompat">
<item name="android:colorBackground">@color/nord_popupBackground</item>
<item name="android:textColor">@color/nord_popupText</item>
</style>
</resources>

View File

@@ -8,6 +8,7 @@ using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Nfc;
using Android.OS;
using Android.Provider;
@@ -640,10 +641,18 @@ namespace Bit.Droid.Services
public bool AutofillAccessibilityServiceRunning()
{
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.ActivityService) as ActivityManager;
var services = manager.GetRunningServices(int.MaxValue);
return services.Any(s => s.Process.ToLowerInvariant().Contains("bitwarden") &&
s.Service.ClassName.ToLowerInvariant().Contains("accessibilityservice"));
}
catch
{
return false;
}
}
public bool AutofillAccessibilityOverlayPermitted()
@@ -732,6 +741,21 @@ namespace Bit.Droid.Services
}
}
public bool UsingDarkTheme()
{
try
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
var app = CrossCurrentActivity.Current.AppContext;
var uiModeFlags = app.Resources.Configuration.UiMode & UiMode.NightMask;
return uiModeFlags == UiMode.NightYes;
}
}
catch { }
return false;
}
public long GetActiveTime()
{
// Returns milliseconds since the system was booted, and includes deep sleep. This clock is guaranteed to
@@ -754,7 +778,13 @@ namespace Bit.Droid.Services
public bool SupportsFido2()
{
return true;
#if !FDROID
if ((int)Build.VERSION.SdkInt >= 21)
{
return true;
}
#endif
return false;
}
private bool DeleteDir(Java.IO.File dir)

View File

@@ -1,71 +0,0 @@
using Android.Graphics;
using Bit.App.Utilities;
using Xamarin.Forms.Platform.Android;
namespace Bit.Droid.Utilities
{
public class ThemeHelpers
{
public static bool LightTheme = true;
public static Color PrimaryColor
{
get => ThemeManager.GetResourceColor("PrimaryColor").ToAndroid();
}
public static Color MutedColor
{
get => ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
public static Color BackgroundColor
{
get => ThemeManager.GetResourceColor("BackgroundColor").ToAndroid();
}
public static Color NavBarBackgroundColor
{
get => ThemeManager.GetResourceColor("NavigationBarBackgroundColor").ToAndroid();
}
public static Color FabColor
{
get => ThemeManager.GetResourceColor("FabColor").ToAndroid();
}
public static Color SwitchOnColor
{
get => ThemeManager.GetResourceColor("SwitchOnColor").ToAndroid();
}
public static Color SwitchThumbColor
{
get => ThemeManager.GetResourceColor("SwitchThumbColor").ToAndroid();
}
public static void SetAppearance(string theme, bool osDarkModeEnabled)
{
SetThemeVariables(theme, osDarkModeEnabled);
}
public static int GetDialogTheme()
{
if (LightTheme)
{
return Android.Resource.Style.ThemeMaterialLightDialog;
}
return Android.Resource.Style.ThemeMaterialDialog;
}
private static void SetThemeVariables(string theme, bool osDarkModeEnabled)
{
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{
theme = "dark";
}
if (theme == "dark" || theme == "black" || theme == "nord")
{
LightTheme = false;
}
else
{
LightTheme = true;
}
}
}
}

View File

@@ -42,6 +42,7 @@ namespace Bit.App.Abstractions
void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings();
bool UsingDarkTheme();
long GetActiveTime();
void CloseMainApp();
bool SupportsFido2();

View File

@@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.3.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2083" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>

View File

@@ -17,12 +17,21 @@ namespace Bit.App
{
public partial class App : Application
{
private readonly MobileI18nService _i18nService;
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ISyncService _syncService;
private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService;
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISettingsService _settingsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISearchService _searchService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
private readonly IStorageService _storageService;
@@ -45,10 +54,20 @@ namespace Bit.App
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
Bootstrap();
@@ -200,7 +219,6 @@ namespace Bit.App
private async void ResumedAsync()
{
UpdateTheme();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync();
@@ -222,7 +240,22 @@ namespace Bit.App
private async Task LogOutAsync(bool expired)
{
await AppHelpers.LogOutAsync();
var userId = await _userService.GetUserIdAsync();
await Task.WhenAll(
_syncService.SetLastSyncAsync(DateTime.MinValue),
_tokenService.ClearTokenAsync(),
_cryptoService.ClearKeysAsync(),
_userService.ClearAsync(),
_settingsService.ClearAsync(userId),
_cipherService.ClearAsync(userId),
_folderService.ClearAsync(userId),
_collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(),
_vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync(),
_deviceActionService.ClearCacheAsync());
_vaultTimeoutService.BiometricLocked = true;
_searchService.ClearIndex();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
@@ -276,7 +309,12 @@ namespace Bit.App
{
return;
}
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
// Will only ever be null - look to remove this in the future
var vaultTimeout = _platformUtilsService.LockTimeout();
if (vaultTimeout == null)
{
vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
}
vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (vaultTimeout == 0)
{
@@ -338,10 +376,6 @@ namespace Bit.App
InitializeComponent();
SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
UpdateTheme();
};
Current.MainPage = new HomePage();
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
@@ -364,15 +398,6 @@ namespace Bit.App
});
}
private void UpdateTheme()
{
Device.BeginInvokeOnMainThread(() =>
{
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
_messagingService.Send("updatedTheme");
});
}
private async Task LockedAsync(bool autoPromptBiometric)
{
await _stateService.PurgeAsync();

View File

@@ -14,7 +14,6 @@
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
@@ -73,9 +72,7 @@
Grid.Row="1"
Grid.ColumnSpan="3"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}"
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
Converter={StaticResource stringHasValueConverter}}"/>
Text="{Binding Cipher.SubTitle}" />
<controls:FaLabel
Grid.Column="1"
Grid.Row="0"

View File

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

View File

@@ -1,25 +0,0 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedStepper : Stepper
{
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public Color StepperBackgroundColor
{
get => (Color)GetValue(StepperBackgroundColorProperty);
set => SetValue(StepperBackgroundColorProperty, value);
}
public Color StepperForegroundColor
{
get => (Color)GetValue(StepperForegroundColorProperty);
set => SetValue(StepperForegroundColorProperty, value);
}
}
}

View File

@@ -1,10 +0,0 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class SelectableLabel : Label
{
}
}

View File

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

View File

@@ -1,187 +0,0 @@
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.Core.Models.Domain;
using Xamarin.Essentials;
namespace Bit.App.Pages
{
public class BaseChangePasswordViewModel : BaseViewModel
{
protected readonly IPlatformUtilsService _platformUtilsService;
protected readonly IUserService _userService;
protected readonly IPolicyService _policyService;
protected readonly IPasswordGenerationService _passwordGenerationService;
protected readonly II18nService _i18nService;
protected readonly ICryptoService _cryptoService;
protected readonly IDeviceActionService _deviceActionService;
protected readonly IApiService _apiService;
protected readonly ISyncService _syncService;
private bool _showPassword;
private bool _isPolicyInEffect;
private string _policySummary;
private MasterPasswordPolicyOptions _policy;
protected BaseChangePasswordViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
}
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
}
public bool IsPolicyInEffect
{
get => _isPolicyInEffect;
set => SetProperty(ref _isPolicyInEffect, value);
}
public string PolicySummary
{
get => _policySummary;
set => SetProperty(ref _policySummary, value);
}
public MasterPasswordPolicyOptions Policy
{
get => _policy;
set => SetProperty(ref _policy, value);
}
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
public async Task InitAsync(bool forceSync = false)
{
if (forceSync)
{
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
await task.ContinueWith(async (t) => await CheckPasswordPolicy());
}
else
{
await CheckPasswordPolicy();
}
}
private async Task CheckPasswordPolicy()
{
Policy = await _policyService.GetMasterPasswordPolicyOptions();
IsPolicyInEffect = Policy?.InEffect() ?? false;
if (!IsPolicyInEffect)
{
return;
}
var bullet = "\n" + "".PadLeft(4) + "\u2022 ";
var sb = new StringBuilder();
sb.Append(_i18nService.T("MasterPasswordPolicyInEffect"));
if (Policy.MinComplexity > 0)
{
sb.Append(bullet)
.Append(string.Format(_i18nService.T("PolicyInEffectMinComplexity"), Policy.MinComplexity));
}
if (Policy.MinLength > 0)
{
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectMinLength"), Policy.MinLength));
}
if (Policy.RequireUpper)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectUppercase"));
}
if (Policy.RequireLower)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectLowercase"));
}
if (Policy.RequireNumbers)
{
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectNumbers"));
}
if (Policy.RequireSpecial)
{
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectSpecial"), "!@#$%^&*"));
}
PolicySummary = sb.ToString();
}
protected async Task<bool> ValidateMasterPasswordAsync()
{
if (Connectivity.NetworkAccess == NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return false;
}
if (string.IsNullOrWhiteSpace(MasterPassword))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
if (IsPolicyInEffect)
{
var userInput = await GetPasswordStrengthUserInput();
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
return false;
}
}
else
{
if (MasterPassword.Length < 8)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
return false;
}
}
if (MasterPassword != ConfirmMasterPassword)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
return true;
}
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)
{
var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled);
var data = rx.Split(email.Substring(0, atPosition).Trim().ToLower());
userInput = new List<string>(data);
}
return userInput;
}
}
}

View File

@@ -40,12 +40,11 @@
HorizontalTextAlignment="Center"></Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
Clicked="LogIn_Clicked"></Button>
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
Clicked="Register_Clicked"></Button>
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
Clicked="LogInSso_Clicked"></Button>
</StackLayout>
</StackLayout>
</StackLayout>

View File

@@ -13,13 +13,11 @@ namespace Bit.App.Pages
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
@@ -28,7 +26,7 @@ namespace Bit.App.Pages
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
UpdateLogo();
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -41,27 +39,6 @@ namespace Bit.App.Pages
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
{
Device.BeginInvokeOnMainThread(() =>
{
UpdateLogo();
});
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage));
}
private void UpdateLogo()
{
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
private void Close_Clicked(object sender, EventArgs e)

View File

@@ -120,9 +120,7 @@
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}"></Button>
<Button Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
<Button Text="{u:I18n Unlock}" Clicked="Unlock_Clicked"></Button>
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -317,9 +317,7 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
var str = PinLock ? Pin : MasterPassword;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
}
public async Task PromptBiometricAsync()

View File

@@ -82,9 +82,7 @@
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -29,8 +29,6 @@ namespace Bit.App.Pages
_vm.Page = this;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
@@ -124,11 +122,5 @@ namespace Bit.App.Pages
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -8,6 +8,11 @@ using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
using Newtonsoft.Json;
using System.Text;
using Xamarin.Essentials;
using System.Text.RegularExpressions;
using Bit.Core.Services;
namespace Bit.App.Pages
{
@@ -73,7 +78,6 @@ namespace Bit.App.Pages
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public Action CloseAction { get; set; }
protected override II18nService i18nService => _i18nService;
@@ -96,14 +100,15 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
AppResources.InternetConnectionRequiredTitle);
return;
}
if (string.IsNullOrWhiteSpace(Email))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (!Email.Contains("@"))
@@ -116,7 +121,8 @@ namespace Bit.App.Pages
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
@@ -157,10 +163,6 @@ namespace Bit.App.Pages
{
StartTwoFactorAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
@@ -177,7 +179,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred);
}
}
}
@@ -185,9 +187,7 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var entry = (Page as LoginPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
(Page as LoginPage).MasterPasswordEntry.Focus();
}
}
}

View File

@@ -37,8 +37,7 @@
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked"></Button>
Clicked="LogIn_Clicked"></Button>
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -32,8 +32,6 @@ namespace Bit.App.Pages
_vm.StartSetPasswordAction = () =>
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
@@ -105,12 +103,6 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task SsoAuthSuccessAsync()
{

View File

@@ -60,7 +60,6 @@ namespace Bit.App.Pages
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public async Task InitAsync()
{
@@ -186,10 +185,6 @@ namespace Bit.App.Pages
else if (response.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{

View File

@@ -137,7 +137,7 @@
<FormattedString>
<Span Text="{u:I18n AcceptPolicies}" />
<Span Text="{u:I18n TermsOfService}"
TextColor="{DynamicResource HyperlinkColor}">
TextColor="{StaticResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/terms/" />
@@ -145,7 +145,7 @@
</Span>
<Span Text=", " />
<Span Text="{u:I18n PrivacyPolicy}"
TextColor="{DynamicResource HyperlinkColor}">
TextColor="{StaticResource HyperlinkColor}">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PoliciesClickCommand}"
CommandParameter="https://bitwarden.com/privacy/" />

View File

@@ -90,47 +90,44 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
AppResources.InternetConnectionRequiredTitle);
return;
}
if (string.IsNullOrWhiteSpace(Email))
{
await _platformUtilsService.ShowDialogAsync(
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (!Email.Contains("@"))
{
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred,
AppResources.Ok);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok);
return;
}
if (string.IsNullOrWhiteSpace(MasterPassword))
{
await _platformUtilsService.ShowDialogAsync(
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (MasterPassword.Length < 8)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
return;
}
if (MasterPassword != ConfirmMasterPassword)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.MasterPasswordConfirmationValMessage, AppResources.Ok);
return;
}
if (ShowTerms && !AcceptPolicies)
{
await _platformUtilsService.ShowDialogAsync(AppResources.AcceptPoliciesError,
AppResources.AnErrorHasOccurred, AppResources.Ok);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
AppResources.AcceptPoliciesError, AppResources.Ok);
return;
}
@@ -193,7 +190,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred);
}
}
}
@@ -201,17 +198,13 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var entry = (Page as RegisterPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
(Page as RegisterPage).MasterPasswordEntry.Focus();
}
public void ToggleConfirmPassword()
{
ShowPassword = !ShowPassword;
var entry = (Page as RegisterPage).ConfirmMasterPasswordEntry;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
(Page as RegisterPage).ConfirmMasterPasswordEntry.Focus();
}
}
}

View File

@@ -32,28 +32,6 @@
StyleClass="text-md"
HorizontalTextAlignment="Start"></Label>
</StackLayout>
<Grid IsVisible="{Binding ResetPasswordAutoEnroll}"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
RowSpacing="0"
ColumnSpacing="0">
@@ -66,7 +44,7 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0, 12, 0, 0"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">

View File

@@ -30,7 +30,6 @@ namespace Bit.App.Pages
private bool _showPassword;
private bool _isPolicyInEffect;
private bool _resetPasswordAutoEnroll;
private string _policySummary;
private MasterPasswordPolicyOptions _policy;
@@ -51,6 +50,7 @@ namespace Bit.App.Pages
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public bool ShowPassword
{
get => _showPassword;
@@ -63,12 +63,6 @@ namespace Bit.App.Pages
get => _isPolicyInEffect;
set => SetProperty(ref _isPolicyInEffect, value);
}
public bool ResetPasswordAutoEnroll
{
get => _resetPasswordAutoEnroll;
set => SetProperty(ref _resetPasswordAutoEnroll, value);
}
public string PolicySummary
{
@@ -90,29 +84,12 @@ namespace Bit.App.Pages
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
public Action SetPasswordSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public Action CloseAction { get; set; }
public string OrgIdentifier { get; set; }
public string OrgId { get; set; }
public async Task InitAsync()
{
await CheckPasswordPolicy();
try
{
var response = await _apiService.GetOrganizationAutoEnrollStatusAsync(OrgIdentifier);
OrgId = response.Id;
ResetPasswordAutoEnroll = response.ResetPasswordEnabled;
}
catch (ApiException e)
{
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
public async Task SubmitAsync()
@@ -194,7 +171,6 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations);
@@ -202,26 +178,8 @@ namespace Bit.App.Pages
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
if (ResetPasswordAutoEnroll)
{
// Grab Organization Keys
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
// Grab user's Encryption Key and encrypt with Org Public Key
var userEncKey = await _cryptoService.GetEncKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
// Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{
ResetPasswordKey = encryptedKey.EncryptedString
};
var userId = await _userService.GetUserIdAsync();
// Enroll user
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
}
await _deviceActionService.HideLoadingAsync();
SetPasswordSuccessAction?.Invoke();
}
catch (ApiException e)

View File

@@ -40,8 +40,6 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
_vm.TwoFactorAuthSuccessAction = () =>
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android)
@@ -77,6 +75,18 @@ namespace Bit.App.Pages
});
}
}
else if (message.Command == "gotFido2Token")
{
var token = (string)message.Data;
if (!string.IsNullOrWhiteSpace(token))
{
Device.BeginInvokeOnMainThread(async () =>
{
_vm.Token = token;
await _vm.SubmitAsync();
});
}
}
else if (message.Command == "resumeYubiKey")
{
if (_vm.YubikeyMethod)
@@ -176,7 +186,14 @@ namespace Bit.App.Pages
{
if (_vm.Fido2Method)
{
await _vm.Fido2AuthenticateAsync();
if (Device.RuntimePlatform == Device.Android)
{
_messagingService.Send("listenFido2TryAgain", true);
}
else
{
await _vm.Fido2AuthenticateAsync();
}
}
else if (_vm.YubikeyMethod)
{
@@ -191,12 +208,6 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _orgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync()
{

View File

@@ -10,6 +10,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Newtonsoft.Json;
@@ -49,7 +51,7 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -113,7 +115,6 @@ namespace Bit.App.Pages
public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public void Init()
{
@@ -152,7 +153,14 @@ namespace Bit.App.Pages
switch (SelectedProviderType.Value)
{
case TwoFactorProviderType.Fido2WebAuthn:
Fido2AuthenticateAsync(providerData);
if (Device.RuntimePlatform == Device.Android)
{
_messagingService.Send("listenFido2", providerData);
}
else
{
Fido2AuthenticateAsync(providerData);
}
break;
case TwoFactorProviderType.YubiKey:
_messagingService.Send("listenYubiKeyOTP", true);
@@ -204,9 +212,7 @@ namespace Bit.App.Pages
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
headerText = AppResources.Fido2Title,
btnText = AppResources.Fido2AuthenticateWebAuthn,
btnReturnText = AppResources.Fido2ReturnToApp,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
@@ -245,14 +251,12 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync();
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
{
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError;
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred,
AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(resultError, AppResources.AnErrorHasOccurred);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser,
AppResources.AnErrorHasOccurred, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2SomethingWentWrong,
AppResources.AnErrorHasOccurred);
}
}
}
@@ -266,14 +270,14 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
AppResources.InternetConnectionRequiredTitle);
return;
}
if (string.IsNullOrWhiteSpace(Token))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.VerificationCode),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred);
return;
}
if (SelectedProviderType == TwoFactorProviderType.Email ||
@@ -297,10 +301,6 @@ namespace Bit.App.Pages
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
@@ -314,7 +314,7 @@ namespace Bit.App.Pages
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred, AppResources.Ok);
AppResources.AnErrorHasOccurred);
}
}
}
@@ -352,7 +352,7 @@ namespace Bit.App.Pages
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
AppResources.InternetConnectionRequiredTitle);
return false;
}
try
@@ -383,8 +383,7 @@ namespace Bit.App.Pages
{
await _deviceActionService.HideLoadingAsync();
}
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent,
AppResources.AnErrorHasOccurred, AppResources.Ok);
await _platformUtilsService.ShowDialogAsync(AppResources.VerificationEmailNotSent);
return false;
}
}

View File

@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.UpdateTempPasswordPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:UpdateTempPasswordPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:UpdateTempPasswordPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n LogOut}" Clicked="LogOut_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout
x:Name="_mainLayout"
Spacing="20">
<StackLayout StyleClass="box">
<Grid Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n UpdateMasterPasswordWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
</Frame>
</Grid>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n RetypeMasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_confirmMasterPassword"
Text="{Binding ConfirmMasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding ToggleConfirmPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n MasterPasswordHint}"
StyleClass="box-label" />
<Entry
x:Name="_hint"
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -1,89 +0,0 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Bit.App.Resources;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class UpdateTempPasswordPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly UpdateTempPasswordPageViewModel _vm;
private readonly string _pageName;
public UpdateTempPasswordPage()
{
// Service Init
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
// Service Use
_messagingService.Send("showStatusBar", true);
// Binding
InitializeComponent();
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);
_vm = BindingContext as UpdateTempPasswordPageViewModel;
_vm.Page = this;
SetActivityIndicator();
// Actions Declaration
_vm.LogOutAction = () =>
{
_messagingService.Send("logout");
};
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
// Link fields that will be referenced in codebehind
MasterPasswordEntry = _masterPassword;
ConfirmMasterPasswordEntry = _confirmMasterPassword;
// Return Types and Commands
_masterPassword.ReturnType = ReturnType.Next;
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
_confirmMasterPassword.ReturnType = ReturnType.Next;
_confirmMasterPassword.ReturnCommand = new Command(() => _hint.Focus());
}
public Entry MasterPasswordEntry { get; set; }
public Entry ConfirmMasterPasswordEntry { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () =>
{
await _vm.InitAsync(true);
});
RequestFocus(_masterPassword);
}
private async void Submit_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async void LogOut_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
AppResources.LogOut, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_vm.LogOutAction();
}
}
}
private void UpdateTempPasswordSuccess()
{
_messagingService.Send("logout");
}
}
}

View File

@@ -1,90 +0,0 @@
using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class UpdateTempPasswordPageViewModel : BaseChangePasswordViewModel
{
public UpdateTempPasswordPageViewModel()
{
PageTitle = AppResources.UpdateMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public Action UpdateTempPasswordSuccessAction { get; set; }
public Action LogOutAction { get; set; }
public void TogglePassword()
{
ShowPassword = !ShowPassword;
(Page as UpdateTempPasswordPage).MasterPasswordEntry.Focus();
}
public void ToggleConfirmPassword()
{
ShowPassword = !ShowPassword;
(Page as UpdateTempPasswordPage).ConfirmMasterPasswordEntry.Focus();
}
public async Task SubmitAsync()
{
if (!await ValidateMasterPasswordAsync())
{
return;
}
// Retrieve details for key generation
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new encKey for the User
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
// Create request
var request = new UpdateTempPasswordRequest
{
Key = newEncKey.Item2.EncryptedString,
NewMasterPasswordHash = masterPasswordHash,
MasterPasswordHint = Hint
};
// Initiate API action
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword);
await _apiService.PutUpdateTempPasswordAsync(request);
await _deviceActionService.HideLoadingAsync();
UpdateTempPasswordSuccessAction?.Invoke();
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.UpdatePasswordError,
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
}
}
}

View File

@@ -4,7 +4,6 @@ using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -23,7 +22,6 @@ namespace Bit.App.Pages
{
if (Device.RuntimePlatform == Device.iOS)
{
On<iOS>().SetUseSafeArea(true);
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
}
@@ -54,8 +52,7 @@ namespace Bit.App.Pages
{
IsRunning = true,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center,
Color = ThemeManager.GetResourceColor("PrimaryColor"),
HorizontalOptions = LayoutOptions.Center
};
if (targetView != null)
{

View File

@@ -27,12 +27,7 @@ namespace Bit.App.Pages
captchaRequiredText = AppResources.CaptchaRequired,
});
var url = environmentService.GetWebVaultUrl();
if (url == null)
{
url = "https://vault.bitwarden.com";
}
url += "/captcha-mobile-connector.html?" + "data=" + data +
var url = environmentService.GetWebVaultUrl() + "/captcha-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=1";
WebAuthenticatorResult authResult = null;

View File

@@ -71,7 +71,6 @@
HorizontalOptions="CenterAndExpand"
LineBreakMode="CharacterWrap" />
<Button Text="{u:I18n RegeneratePassword}"
StyleClass="btn-primary"
HorizontalOptions="FillAndExpand"
Clicked="Regenerate_Clicked"></Button>
<Button Text="{u:I18n CopyPassword}"
@@ -109,7 +108,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
<Stepper
Value="{Binding NumWords}"
Maximum="20"
Minimum="3"
@@ -240,7 +239,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
<Stepper
Value="{Binding MinNumber}"
Maximum="5"
Minimum="0"
@@ -260,7 +259,7 @@
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
<Stepper
Value="{Binding MinSpecial}"
Maximum="5"
Minimum="0"

View File

@@ -1,8 +1,6 @@
using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -11,8 +9,6 @@ namespace Bit.App.Pages
{
public partial class GeneratorPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private GeneratorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action<string> _selectAction;
@@ -22,7 +18,6 @@ namespace Bit.App.Pages
{
_tabsPage = tabsPage;
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_vm = BindingContext as GeneratorPageViewModel;
_vm.Page = this;
_fromTabPage = fromTabPage;
@@ -65,22 +60,6 @@ namespace Bit.App.Pages
{
await InitAsync();
}
_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
{
if (message.Command == "updatedTheme")
{
Device.BeginInvokeOnMainThread(() =>
{
_vm.RedrawPassword();
});
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
protected override bool OnBackButtonPressed()

View File

@@ -262,14 +262,6 @@ namespace Bit.App.Pages
await _passwordGenerationService.AddHistoryAsync(Password);
}
public void RedrawPassword()
{
if (!string.IsNullOrEmpty(_password))
{
TriggerPropertyChanged(nameof(ColoredPassword));
}
}
public async Task SaveOptionsAsync(bool regenerate = true)
{
if (!_doneIniting)

View File

@@ -54,6 +54,26 @@
x:Name="_deleteItem"
x:Key="deleteItem" />
<Style x:Key="segmentedButtonBase" TargetType="Button">
<Setter Property="HeightRequest" Value="{Binding SegmentedButtonHeight}" />
<Setter Property="FontSize" Value="{Binding SegmentedButtonFontSize}" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style x:Key="segmentedButtonNormal" BaseResourceKey="segmentedButtonBase" TargetType="Button">
<Setter Property="TextColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="FontAttributes" Value="None" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="BorderColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="BorderWidth" Value="1" />
</Style>
<Style x:Key="segmentedButtonDisabled" BaseResourceKey="segmentedButtonBase" TargetType="Button">
<Setter Property="TextColor" Value="{StaticResource TitleTextColor}" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="BorderWidth" Value="0" />
</Style>
<ScrollView x:Key="scrollView" x:Name="_scrollView">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
@@ -111,25 +131,51 @@
</Grid.ColumnDefinitions>
<Button
Text="{u:I18n TypeFile}"
StyleClass="segmented-button"
IsEnabled="{Binding IsText}"
HeightRequest="{Binding SegmentedButtonHeight}"
FontSize="{Binding SegmentedButtonFontSize}"
Clicked="FileType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}"
Grid.Column="0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonNormal}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
<Button
Text="{u:I18n TypeText}"
StyleClass="segmented-button"
IsEnabled="{Binding IsFile}"
HeightRequest="{Binding SegmentedButtonHeight}"
FontSize="{Binding SegmentedButtonFontSize}"
Clicked="TextType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}"
Grid.Column="1">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonNormal}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Style"
Value="{StaticResource segmentedButtonDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</Grid>
</StackLayout>
@@ -243,20 +289,20 @@
Text="{u:I18n Options}"
x:Name="_btnOptions"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
TextColor="{StaticResource PrimaryColor}"
Margin="0" />
<controls:FaButton
x:Name="_btnOptionsUp"
Text="&#xf077;"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
TextColor="{StaticResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="False" />
<controls:FaButton
x:Name="_btnOptionsDown"
Text="&#xf078;"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
TextColor="{StaticResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="False" />
</StackLayout>
@@ -375,7 +421,7 @@
MaxLength="9"
TextChanged="OnMaxAccessCountTextChanged"
HorizontalOptions="FillAndExpand" />
<controls:ExtendedStepper
<Stepper
x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}"
Maximum="999999999"

View File

@@ -61,6 +61,7 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
await _vm.InitAsync();
if (_syncService.SyncInProgress)
{
IsBusy = true;
@@ -161,7 +162,7 @@ namespace Bit.App.Pages
if (DoOnce())
{
var page = new SendsPage(_vm.Filter, _vm.Type != null);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new NavigationPage(page), false);
}
}

View File

@@ -110,6 +110,11 @@ namespace Bit.App.Pages
public Command<SendView> SendOptionsCommand { get; set; }
public bool LoadedOnce { get; set; }
public async Task InitAsync()
{
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
}
public async Task LoadAsync()
{
if (_doingLoad)
@@ -137,7 +142,6 @@ namespace Bit.App.Pages
ShowNoData = false;
Loading = true;
ShowList = false;
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
var groupedSends = new List<SendGroupingsPageListGroup>();
var page = Page as SendGroupingsPage;

View File

@@ -29,7 +29,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleAutofillService"
StyleClass="box-overlay"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AutofillServiceSwitch, Property=Width}"
@@ -58,7 +58,8 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleInlineAutofill"
StyleClass="box-overlay"
IsEnabled="{Binding InlineAutofillEnabled}"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=InlineAutofillSwitch, Property=Width}"
@@ -84,7 +85,7 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleAccessibility"
StyleClass="box-overlay"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AccessibilitySwitch, Property=Width}"
@@ -113,7 +114,8 @@
HorizontalOptions="End" />
<Button
Clicked="ToggleDrawOver"
StyleClass="box-overlay"
IsEnabled="{Binding DrawOverEnabled}"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=DrawOverSwitch, Property=Width}"

View File

@@ -170,10 +170,6 @@ namespace Bit.App.Pages
public void ToggleInlineAutofill()
{
if (!InlineAutofillEnabled)
{
return;
}
InlineAutofillToggled = !InlineAutofillToggled;
}
@@ -184,10 +180,6 @@ namespace Bit.App.Pages
public void ToggleDrawOver()
{
if (!DrawOverEnabled)
{
return;
}
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
}

View File

@@ -27,18 +27,6 @@
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Frame
IsVisible="{Binding DisablePrivateVaultPolicyEnabled}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n FileFormat}"
@@ -48,8 +36,7 @@
ItemsSource="{Binding FileFormatOptions, Mode=OneTime}"
SelectedIndex="{Binding FileFormatSelectedIndex}"
SelectedIndexChanged="FileFormat_Changed"
StyleClass="box-value"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}" />
StyleClass="box-value" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
@@ -72,7 +59,6 @@
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
@@ -94,8 +80,7 @@
<Button Text="{u:I18n ExportVault}"
Clicked="ExportVault_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
VerticalOptions="End" />
</StackLayout>
</StackLayout>
</StackLayout>

View File

@@ -4,11 +4,8 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Enums;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
@@ -23,7 +20,6 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService;
private readonly ICryptoService _cryptoService;
private readonly IExportService _exportService;
private readonly IPolicyService _policyService;
private int _fileFormatSelectedIndex;
private string _exportWarningMessage;
@@ -40,7 +36,6 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
PageTitle = AppResources.ExportVault;
TogglePasswordCommand = new Command(TogglePassword);
@@ -58,22 +53,10 @@ namespace Bit.App.Pages
{
_initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UpdateWarning();
}
public List<KeyValuePair<string, string>> FileFormatOptions { get; set; }
private bool _disabledPrivateVaultPolicyEnabled = false;
public bool DisablePrivateVaultPolicyEnabled
{
get => _disabledPrivateVaultPolicyEnabled;
set
{
SetProperty(ref _disabledPrivateVaultPolicyEnabled, value);
}
}
public int FileFormatSelectedIndex
{

View File

@@ -31,7 +31,8 @@
</StackLayout>
<Label
StyleClass="box-footer-label"
Text="{u:I18n ThemeDescription}" />
Text="{u:I18n ThemeDescription}"
x:Name="_themeDescriptionLabel" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">

View File

@@ -25,6 +25,8 @@ namespace Bit.App.Pages
{
ToolbarItems.RemoveAt(0);
_vm.ShowAndroidAutofillSettings = _deviceActionService.SupportsAutofillService();
_themeDescriptionLabel.Text = string.Concat(_themeDescriptionLabel.Text, " ",
AppResources.RestartIsRequired);
}
else
{

View File

@@ -59,7 +59,7 @@ namespace Bit.App.Pages
}
ThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
new KeyValuePair<string, string>(null, AppResources.Default),
new KeyValuePair<string, string>("light", AppResources.Light),
new KeyValuePair<string, string>("dark", AppResources.Dark),
new KeyValuePair<string, string>("black", AppResources.Black),
@@ -215,8 +215,17 @@ namespace Bit.App.Pages
{
var theme = ThemeOptions[ThemeSelectedIndex].Key;
await _storageService.SaveAsync(Constants.ThemeKey, theme);
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Application.Current.Resources);
_messagingService.Send("updatedTheme");
if (Device.RuntimePlatform == Device.Android)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Restarting);
await Task.Delay(1000);
}
_messagingService.Send("updatedTheme", theme);
if (Device.RuntimePlatform == Device.iOS)
{
await Task.Delay(500);
await _platformUtilsService.ShowDialogAsync(AppResources.ThemeAppliedOnRestart);
}
}
}

View File

@@ -21,38 +21,18 @@
x:DataType="pages:SettingsPageListItem">
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<Frame
IsVisible="{Binding UseFrame}"
Padding="10"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{Binding Name, Mode=OneWay}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<Label IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
Text="{Binding Name, Mode=OneWay}"
LineBreakMode="{Binding LineBreakMode}"
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding SubLabel, Mode=OneWay}"
IsVisible="{Binding ShowSubLabel}"
IsVisible="{Binding SubLabel, Converter={StaticResource stringHasValue}}"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" />
<TimePicker IsVisible="{Binding ShowTimeInput}"
Time="{Binding Time}" Format="HH:mm"
PropertyChanged="OnTimePickerPropertyChanged"
HorizontalOptions="End"
VerticalOptions="Center"
FontSize="Small"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" Margin="-5" />
</controls:ExtendedStackLayout>
</DataTemplate>

View File

@@ -1,5 +1,4 @@
using System.ComponentModel;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
using System.Linq;
@@ -44,16 +43,6 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed();
}
async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args)
{
var s = (TimePicker) sender;
var time = s.Time.TotalMinutes;
if (s.IsFocused && args.PropertyName == "Time")
{
await _vm.VaultTimeoutAsync(false, (int)time);
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;

View File

@@ -1,5 +1,4 @@
using System;
using Bit.App.Resources;
using Bit.App.Resources;
using Bit.App.Utilities;
using System.Collections.Generic;
using Xamarin.Forms;
@@ -11,12 +10,7 @@ namespace Bit.App.Pages
public string Icon { get; set; }
public string Name { get; set; }
public string SubLabel { get; set; }
public TimeSpan? Time { get; set; }
public bool UseFrame { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
public bool ShowSubLabel => SubLabel.Length != 0;
public bool ShowTimeInput => Time != null;
public Color SubLabelColor => SubLabelTextEnabled ?
ThemeManager.GetResourceColor("SuccessColor") :
ThemeManager.GetResourceColor("MutedColor");

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