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

Compare commits

..

1 Commits

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

View File

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

View File

@@ -59,10 +59,6 @@ jobs:
name: Android
runs-on: windows-2022
needs: setup
strategy:
fail-fast: false
matrix:
variant: ['prod', 'qa']
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
@@ -71,7 +67,7 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
@@ -91,6 +87,7 @@ jobs:
Write-Host "components were not installed"
exit 1
}
- name: Print environment
run: |
nuget help | grep Version
@@ -101,8 +98,7 @@ jobs:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
fetch-depth: 0
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -113,17 +109,12 @@ jobs:
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
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: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
@@ -151,35 +142,26 @@ jobs:
run: dotnet test test/Core.Test/Core.Test.csproj
- name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }}
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Setup Android build (${{ matrix.variant }})
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build Android
- 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"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh
- name: Sign Android Build
- name: Sign for Play Store
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
@@ -193,8 +175,9 @@ jobs:
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
$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 "########################################"
@@ -210,41 +193,33 @@ jobs:
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
$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 Prod .aab artifact
if: ${{ matrix.variant == 'prod' }}
- name: Upload Play Store .aab artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
if-no-files-found: error
- name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
- name: Upload Play Store .apk artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
if-no-files-found: error
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
if-no-files-found: error
- name: Deploy to Play Store
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }}
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"

3
.gitignore vendored
View File

@@ -208,5 +208,4 @@ FakesAssemblies/
# Other
project.lock.json
.DS_Store
src/App/Css
tools
src/App/Css

View File

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

View File

@@ -156,7 +156,6 @@
<Compile Include="Services\FileService.cs" />
<Compile Include="Services\AutofillHandler.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" />

Binary file not shown.

View File

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

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.11.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.10.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />

View File

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

View File

@@ -139,7 +139,6 @@
<Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" />
<Folder Include="Controls\IconLabelButton\" />
</ItemGroup>
<ItemGroup>
@@ -431,6 +430,5 @@
<None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -100,7 +100,7 @@ namespace Bit.App.Pages
private async Task UpdateRequestTime()
{
TriggerPropertyChanged(nameof(TimeOfRequestText));
if (LoginRequest?.IsExpired ?? false)
if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
{
StopRequestTimeUpdater();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
@@ -110,7 +110,7 @@ namespace Bit.App.Pages
private async Task PasswordlessLoginAsync(bool approveRequest)
{
if (LoginRequest.IsExpired)
if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
{
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
await Page.Navigation.PopModalAsync();
@@ -171,7 +171,5 @@ namespace Bit.App.Pages
public string DeviceType { get; set; }
public string IpAddress { get; set; }
public bool IsExpired => RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
}
}

View File

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

View File

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

View File

@@ -2893,15 +2893,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Get master password hint.
/// </summary>
public static string GetMasterPasswordwordHint {
get {
return ResourceManager.GetString("GetMasterPasswordwordHint", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Get your master password hint.
/// </summary>
@@ -3415,15 +3406,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Logging in as {0}.
/// </summary>
public static string LoggingInAsX {
get {
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log In.
/// </summary>
@@ -3452,11 +3434,10 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Login attempt from:
///{0}
///Do you want to switch to this account?.
/// Looks up a localized string similar to Login attempt from {0}. Do you want to switch to this account?.
/// </summary>
public static string LoginAttemptFromXDoYouWantToSwitchToThisAccount {
public static string LoginAttemptFromXDoYouWantToSwitchToThisAccount
{
get {
return ResourceManager.GetString("LoginAttemptFromXDoYouWantToSwitchToThisAccount", resourceCulture);
}
@@ -3561,24 +3542,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Log In with another device.
/// </summary>
public static string LogInWithAnotherDevice {
get {
return ResourceManager.GetString("LogInWithAnotherDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log In with master password.
/// </summary>
public static string LogInWithMasterPassword {
get {
return ResourceManager.GetString("LogInWithMasterPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log out.
/// </summary>
@@ -3984,15 +3947,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to New around here?.
/// </summary>
public static string NewAroundHere {
get {
return ResourceManager.GetString("NewAroundHere", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New custom field.
/// </summary>
@@ -4227,15 +4181,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Not you?.
/// </summary>
public static string NotYou {
get {
return ResourceManager.GetString("NotYou", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This login does not have a username or password configured..
/// </summary>

View File

@@ -1762,7 +1762,7 @@ Scanning will happen automatically.</value>
<value>Syncing vault with pull down gesture.</value>
</data>
<data name="LogInSso" xml:space="preserve">
<value>Enterprise single sign-on</value>
<value>Enterprise Single Sign-On</value>
</data>
<data name="LogInSsoSummary" xml:space="preserve">
<value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value>
@@ -2473,22 +2473,4 @@ select Add TOTP to store the key safely</value>
{0}
Do you want to switch to this account?</value>
</data>
<data name="NewAroundHere" xml:space="preserve">
<value>New around here?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Get master password hint</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Not you?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Log in with master password</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Log In with another device</value>
</data>
</root>

View File

@@ -157,7 +157,7 @@ namespace Bit.App.Services
};
_pushNotificationService.Value.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
_messagingService.Value.Send(Constants.PasswordlessLoginRequestKey, passwordlessLoginMessage);
_messagingService.Value.Send("passwordlessLoginRequest", passwordlessLoginMessage);
break;
default:
break;
@@ -228,9 +228,7 @@ namespace Bit.App.Services
if (data is PasswordlessNotificationData passwordlessNotificationData)
{
var notificationUserId = await _stateService.Value.GetUserIdAsync(passwordlessNotificationData.UserEmail);
var activeUserEmail = await _stateService.Value.GetActiveUserEmailAsync();
var notificationSaved = await _stateService.Value.GetPasswordlessLoginNotificationAsync();
if (activeUserEmail != passwordlessNotificationData.UserEmail && notificationUserId != null && notificationSaved != null)
if (notificationUserId != null)
{
await _stateService.Value.SetActiveUserAsync(notificationUserId);
_messagingService.Value.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);

View File

@@ -80,7 +80,6 @@
Value="{DynamicResource StepperForegroundColor}" />
</Style>
<Style TargetType="Frame"
ApplyToDerivedTypes="True"
Class="btn-icon-row">
<Setter Property="BackgroundColor"
Value="{DynamicResource ButtonBackgroundColor}" />

View File

@@ -130,36 +130,7 @@
<Setter Property="BackgroundColor"
Value="{DynamicResource FabColor}" />
</Style>
<Style TargetType="controls:IconLabelButton"
ApplyToDerivedTypes="True"
Class="btn-icon-secondary">
<Setter Property="IconLabelColor"
Value="{DynamicResource ButtonTextColorOpacity}" />
<Setter Property="IconLabelBorderColor"
Value="{DynamicResource ButtonBorderColor}" />
<Setter Property="IconLabelBackgroundColor"
Value="{DynamicResource BackgroundColor}" />
<Setter Property="CornerRadius"
Value="5" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="IconLabelColor"
Value="{DynamicResource ButtonTextColorDisabled}" />
<Setter Property="IconLabelBackgroundColor"
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
<Setter Property="IconLabelBorderColor"
Value="{DynamicResource ButtonBackgroundColorDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!-- Title -->
<Style TargetType="Button"
Class="btn-title"
@@ -547,19 +518,4 @@
<Setter Property="Radius"
Value="15" />
</Style>
<Style TargetType="Entry" Class="entry-visual-disabled">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor"
Value="{DynamicResource MutedColor}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -93,7 +93,6 @@
Value="{DynamicResource StepperForegroundColor}" />
</Style>
<Style TargetType="Frame"
ApplyToDerivedTypes="True"
Class="btn-icon-row">
<Setter Property="BackgroundColor"
Value="{DynamicResource ButtonBackgroundColor}" />

View File

@@ -7,7 +7,6 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Utilities.AccountManagement
@@ -104,12 +103,11 @@ namespace Bit.App.Utilities.AccountManagement
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
await _stateService.SetRememberedEmailAsync(email);
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(true));
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
}
else
{
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(false));
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
}
}
}
@@ -185,7 +183,7 @@ namespace Bit.App.Utilities.AccountManagement
await Device.InvokeOnMainThreadAsync(() =>
{
Options.HideAccountSwitcher = false;
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(false));
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
});
}
@@ -220,17 +218,5 @@ namespace Bit.App.Utilities.AccountManagement
_messagingService.Send(AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED);
});
}
public async Task PromptToSwitchToExistingAccountAsync(string userId)
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
}
}
}

View File

@@ -1,14 +0,0 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class HomeNavigationParams : INavigationParams
{
public HomeNavigationParams(bool shouldCheckRememberEmail)
{
ShouldCheckRememberEmail = shouldCheckRememberEmail;
}
public bool ShouldCheckRememberEmail { get; }
}
}

View File

@@ -83,10 +83,8 @@ namespace Bit.Core.Abstractions
Task<SendResponse> PutSendAsync(string id, SendRequest request);
Task<SendResponse> PutSendRemovePasswordAsync(string id);
Task DeleteSendAsync(string id);
Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync();
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
}
}

View File

@@ -27,7 +27,6 @@ namespace Bit.Core.Abstractions
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);

View File

@@ -13,7 +13,6 @@ namespace Bit.Core.Abstractions
{
List<AccountView> AccountViews { get; }
Task<string> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync();
Task<bool> IsActiveAccountAsync(string userId = null);
Task SetActiveUserAsync(string userId);
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();

View File

@@ -14,7 +14,5 @@ namespace Bit.Core.Abstractions
Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification);
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
// Passwordless code will be moved to an independent service in future techdept
Task SyncPasswordlessLoginRequestsAsync();
}
}

View File

@@ -112,7 +112,5 @@
public const string File = "\xe96e";
public const string Paste = "\xe96f";
public const string ViewCellMenu = "\xe5d3";
public const string Device = "\xe986";
public const string Suitcase = "\xe98c";
}
}

View File

@@ -37,7 +37,6 @@
public const string iOSNotificationClearActionId = "Clear";
public const string NotificationData = "notificationData";
public const string NotificationDataType = "Type";
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44;

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Response
{
@@ -15,19 +13,7 @@ namespace Bit.Core.Models.Response
public string Key { get; set; }
public string MasterPasswordHash { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? ResponseDate { get; set; }
public bool? RequestApproved { get; set; }
public bool RequestApproved { get; set; }
public string Origin { get; set; }
public string RequestAccessCode { get; set; }
public Tuple<byte[], byte[]> RequestKeyPair { get; set; }
public bool IsAnswered => RequestApproved != null && ResponseDate != null;
public bool IsExpired => CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
}
public class PasswordlessLoginsResponse
{
public List<PasswordlessLoginResponse> Data { get; set; }
}
}

View File

@@ -536,12 +536,6 @@ namespace Bit.Core.Services
#region PasswordlessLogin
public async Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync()
{
var response = await SendAsync<object, PasswordlessLoginsResponse>(HttpMethod.Get, $"/auth-requests/", null, true, true);
return response.Data;
}
public Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id)
{
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
@@ -553,11 +547,6 @@ namespace Bit.Core.Services
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
}
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
{
return SendAsync<object, bool>(HttpMethod.Get, $"/devices/knowndevice/{email}/{deviceIdentifier}", null, false, true);
}
#endregion
#region Helpers
@@ -801,6 +790,8 @@ namespace Bit.Core.Services
if (authed
&&
(
(tokenError && response.StatusCode == HttpStatusCode.BadRequest)
||
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
||
response.StatusCode == HttpStatusCode.Forbidden
@@ -817,17 +808,6 @@ namespace Bit.Core.Services
var responseJsonString = await response.Content.ReadAsStringAsync();
responseJObject = JObject.Parse(responseJsonString);
}
if (authed && tokenError
&&
response.StatusCode == HttpStatusCode.BadRequest
&&
responseJObject?["error"]?.ToString() == "invalid_grant")
{
await _logoutCallbackAsync(new Tuple<string, bool, bool>(null, false, true));
return null;
}
return new ErrorResponse(responseJObject, response.StatusCode, tokenError);
}
catch

View File

@@ -471,11 +471,6 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null;
}
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
{
return await _apiService.GetAuthRequestAsync();
}
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{
return await _apiService.GetAuthRequestAsync(id);

View File

@@ -46,12 +46,6 @@ namespace Bit.Core.Services
return activeUserId;
}
public async Task<string> GetActiveUserEmailAsync()
{
var activeUserId = await GetActiveUserIdAsync();
return await GetEmailAsync(activeUserId);
}
public async Task<bool> IsActiveAccountAsync(string userId = null)
{
if (userId == null)
@@ -74,6 +68,7 @@ namespace Bit.Core.Services
_state.ActiveUserId = userId;
// Update pre-auth settings based on now-active user
await SetRememberedEmailAsync(await GetEmailAsync());
await SetRememberedOrgIdentifierAsync(await GetRememberedOrgIdentifierAsync());
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
}

View File

@@ -24,7 +24,6 @@ namespace Bit.Core.Services
private readonly IPolicyService _policyService;
private readonly ISendService _sendService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger;
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
public SyncService(
@@ -40,7 +39,6 @@ namespace Bit.Core.Services
IPolicyService policyService,
ISendService sendService,
IKeyConnectorService keyConnectorService,
ILogger logger,
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
{
_stateService = stateService;
@@ -55,7 +53,6 @@ namespace Bit.Core.Services
_policyService = policyService;
_sendService = sendService;
_keyConnectorService = keyConnectorService;
_logger = logger;
_logoutCallbackAsync = logoutCallbackAsync;
}
@@ -385,45 +382,5 @@ namespace Bit.Core.Services
new Dictionary<string, SendData>();
await _sendService.ReplaceAsync(sends);
}
public async Task SyncPasswordlessLoginRequestsAsync()
{
try
{
var userId = await _stateService.GetActiveUserIdAsync();
// if the user has not enabled passwordless logins ignore requests
if (!await _stateService.GetApprovePasswordlessLoginsAsync(userId))
{
return;
}
var loginRequests = await _apiService.GetAuthRequestAsync();
if (loginRequests == null || !loginRequests.Any())
{
return;
}
var validLoginRequest = loginRequests.Where(l => !l.IsAnswered && !l.IsExpired)
.OrderByDescending(x => x.CreationDate)
.FirstOrDefault();
if (validLoginRequest is null)
{
return;
}
await _stateService.SetPasswordlessLoginNotificationAsync(new PasswordlessRequestNotification()
{
Id = validLoginRequest.Id,
UserId = userId
});
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
}
}

View File

@@ -29,8 +29,6 @@ namespace Bit.Core.Utilities
var messagingService = Resolve<IMessagingService>("messagingService");
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
var cryptoService = Resolve<ICryptoService>("cryptoService");
var logger = Resolve<ILogger>();
SearchService searchService = null;
var tokenService = new TokenService(stateService);
@@ -69,7 +67,7 @@ namespace Bit.Core.Utilities
});
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
keyConnectorService, logger, (extras) =>
keyConnectorService, (extras) =>
{
messagingService.Send("logout", extras);
return Task.CompletedTask;

View File

@@ -425,16 +425,15 @@ namespace Bit.iOS.Autofill
}
}
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
private void LaunchHomePage()
{
var appOptions = new AppOptions { IosExtension = true };
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
var app = new App.App(appOptions);
var homePage = new HomePage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(homePage);
if (homePage.BindingContext is HomeViewModel vm)
{
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
@@ -457,8 +456,8 @@ namespace Bit.iOS.Autofill
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(environmentPage);
@@ -476,7 +475,7 @@ namespace Bit.iOS.Autofill
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(registerPage);
@@ -496,9 +495,8 @@ namespace Bit.iOS.Autofill
{
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginPage);
@@ -604,14 +602,7 @@ namespace Bit.iOS.Autofill
switch (navTarget)
{
case NavigationTarget.HomeLogin:
if (navParams is HomeNavigationParams homeParams)
{
DismissViewController(false, () => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
}
else
{
DismissViewController(false, () => LaunchHomePage());
}
DismissViewController(false, () => LaunchHomePage());
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key>
<string>2022.11.0</string>
<string>2022.10.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleLocalizations</key>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key>
<string>2022.11.0</string>
<string>2022.10.1</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>

View File

@@ -446,16 +446,15 @@ namespace Bit.iOS.Extension
});
}
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
private void LaunchHomePage()
{
var appOptions = new AppOptions { IosExtension = true };
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
var app = new App.App(appOptions);
var homePage = new HomePage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(homePage);
if (homePage.BindingContext is HomeViewModel vm)
{
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
@@ -478,8 +477,8 @@ namespace Bit.iOS.Extension
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(environmentPage);
@@ -497,7 +496,7 @@ namespace Bit.iOS.Extension
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(registerPage);
@@ -517,9 +516,8 @@ namespace Bit.iOS.Extension
{
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => CompleteRequest(null, null);
}
var navigationPage = new NavigationPage(loginPage);

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2022.11.0</string>
<string>2022.10.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>MinimumOSVersion</key>

View File

@@ -287,13 +287,13 @@ namespace Bit.iOS.ShareExtension
return _app;
}
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
private void LaunchHomePage()
{
var homePage = new HomePage(_appOptions.Value, shouldCheckRememberEmail);
var homePage = new HomePage();
SetupAppAndApplyResources(homePage);
if (homePage.BindingContext is HomeViewModel vm)
{
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow());
vm.StartRegisterAction = () => DismissAndLaunch(() => LaunchRegisterFlow());
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.StartEnvironmentAction = () => DismissAndLaunch(() => LaunchEnvironmentFlow());
@@ -311,8 +311,8 @@ namespace Bit.iOS.ShareExtension
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(environmentPage);
@@ -325,7 +325,7 @@ namespace Bit.iOS.ShareExtension
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(registerPage);
}
@@ -338,9 +338,11 @@ namespace Bit.iOS.ShareExtension
{
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
vm.LogInSuccessAction = () =>
{
DismissLockAndContinue();
};
vm.CloseAction = () => CompleteRequest();
}
NavigateToPage(loginPage);
@@ -424,14 +426,7 @@ namespace Bit.iOS.ShareExtension
switch (navTarget)
{
case NavigationTarget.HomeLogin:
if (navParams is HomeNavigationParams homeParams)
{
ExecuteLaunch(() => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
}
else
{
ExecuteLaunch(() => LaunchHomePage());
}
ExecuteLaunch(LaunchHomePage);
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)

View File

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

Binary file not shown.