1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-25 22:03:17 +00:00

Compare commits

..

10 Commits

Author SHA1 Message Date
Jacob Fink
a484ca633c Merge branch 'beeep-totp' of https://github.com/bitwarden/mobile into beeep-totp 2022-06-06 08:58:52 -04:00
André Bispo
655b51b6a5 Merge branch 'master' into beeep-totp
# Conflicts:
#	src/App/App.csproj
2022-06-04 18:05:14 +01:00
Jacob Fink
0b626cedc7 got the countdown working 2022-03-04 16:44:22 -05:00
Jacob Fink
3ac2580742 add toolbar icons 2022-02-17 11:26:14 -05:00
Jacob Fink
008ed8eb56 add authentication view cell 2022-02-16 15:55:02 -05:00
Jacob Fink
26e0e43bb4 Merge branch 'master' into beeep-totp 2022-02-16 09:25:29 -05:00
Jacob Fink
0ad992faec add tab page 2022-02-16 09:25:19 -05:00
Jacob Fink
98dd8298ea clear extra code and fix build 2022-02-15 14:15:42 -05:00
Jacob Fink
bb37bac620 Revert config files from previous commit
This reverts commit b02c58e362.
2022-02-15 13:36:32 -05:00
Carlos J. Muentes
b02c58e362 Initial commit of new TOTP page 2022-02-11 15:22:51 -05:00
95 changed files with 5113 additions and 7628 deletions

View File

@@ -22,7 +22,6 @@
## Before you submit
- [ ] I have checked for formatting errors (`dotnet tool run dotnet-format --check`) (required)
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -60,11 +60,6 @@ jobs:
runs-on: windows-2019
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -214,11 +209,6 @@ jobs:
name: F-Droid Build
runs-on: windows-2019
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -378,11 +368,6 @@ jobs:
runs-on: macos-11
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Print environment
run: |
nuget help | grep Version

View File

@@ -34,13 +34,29 @@ jobs:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
file: src/Android/Properties/AndroidManifest.xml
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/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
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
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: Get branch name
id: branch
@@ -67,8 +83,8 @@ jobs:
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip"
commit: ${{ github.sha }}
tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.version.outputs.version }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true

View File

@@ -84,7 +84,7 @@
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version>

View File

@@ -12,6 +12,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Droid.Services;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
using Xamarin.Android.Net;
@@ -19,7 +20,6 @@ using System.Net.Http;
using System.Net;
using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -62,15 +62,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
}
#if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -130,14 +121,13 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
var deviceActionService = new DeviceActionService(stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
@@ -152,7 +142,7 @@ namespace Bit.Droid
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);

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.6.2" 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.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Plugin.CurrentActivity;
@@ -26,41 +26,13 @@ namespace Bit.Droid.Services
PendingIntentFlags.UpdateCurrent));
}
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await Clipboard.SetTextAsync(text);
await ClearClipboardAlarmAsync(expiresInMs);
}
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive)
{
clipData.Description.Extras ??= new PersistableBundle();
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
}
clipboardManager.PrimaryClip = clipData;
}
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
{
var clearMs = expiresInMs;

View File

@@ -35,7 +35,6 @@ namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
private readonly IClipboardService _clipboardService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
@@ -48,13 +47,11 @@ namespace Bit.Droid.Services
private string _userAgent;
public DeviceActionService(
IClipboardService clipboardService,
IStateService stateService,
IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_clipboardService = clipboardService;
_stateService = stateService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
@@ -932,12 +929,20 @@ namespace Bit.Droid.Services
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
await _clipboardService.CopyTextAsync(totp);
CopyToClipboard(totp);
}
}
}
}
private void CopyToClipboard(string text)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
}
public float GetSystemFontSizeScale()
{
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;

View File

@@ -1,12 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions
{
public interface IAccountsManager
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
}
}

View File

@@ -1,14 +0,0 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
public interface INavigationParams { }
public interface IAccountsManagerHost
{
Task SetPreviousPageInfoAsync();
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
Task UpdateThemeAsync();
}
}

View File

@@ -13,12 +13,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>
@@ -127,8 +127,8 @@
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
<Folder Include="Pages\Authenticator\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
</ItemGroup>
<ItemGroup>
@@ -421,6 +421,5 @@
<None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
</ItemGroup>
</Project>

View File

@@ -6,7 +6,6 @@ using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -17,7 +16,7 @@ using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
{
public partial class App : Application, IAccountsManagerHost
public partial class App : Application
{
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
@@ -28,7 +27,6 @@ namespace Bit.App
private readonly IAuthService _authService;
private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService;
private readonly IAccountsManager _accountsManager;
private static bool _isResumed;
@@ -49,9 +47,6 @@ namespace Bit.App
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => Options, this);
Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) =>
@@ -76,6 +71,30 @@ namespace Bit.App
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "locked")
{
var extras = message.Data as Tuple<string, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
else if (message.Command == "lockVault")
{
await _vaultTimeoutService.LockAsync(true);
}
else if (message.Command == "logout")
{
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
}
else if (message.Command == "resumed")
{
if (Device.RuntimePlatform == Device.iOS)
@@ -90,10 +109,22 @@ namespace Bit.App
await SleptAsync();
}
}
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
await SetMainPageAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
@@ -137,6 +168,7 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
});
}
@@ -202,7 +234,6 @@ namespace Bit.App
private async Task ResumedAsync()
{
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
await UpdateThemeAsync();
@@ -232,6 +263,102 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar();
}
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync(userId, userInitiated);
await SetMainPageAsync();
_authService.LogOut(() =>
{
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Options.HideAccountSwitcher = false;
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
await UpdateThemeAsync();
});
}
private async Task SetMainPageAsync()
{
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
}
else if (Options.Uri != null)
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);
}
}
else
{
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
}
private async Task ClearCacheIfNeededAsync()
{
var lastClear = await _stateService.GetLastFileCacheClearAsync();
@@ -293,7 +420,7 @@ namespace Bit.App
UpdateThemeAsync();
};
Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -314,8 +441,23 @@ namespace Bit.App
});
}
public async Task SetPreviousPageInfoAsync()
private async Task LockedAsync(string userId, bool userInitiated)
{
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{
@@ -341,44 +483,8 @@ namespace Bit.App
}
}
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
}
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
}
break;
case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams)
{
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
break;
case NavigationTarget.Home:
Current.MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
break;
case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
break;
}
var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
}
}
}

View File

@@ -63,10 +63,6 @@ namespace Bit.App.Controls
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
public Action AfterHide { get; set; }
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
@@ -139,8 +135,6 @@ namespace Bit.App.Controls
// remove overlay
IsVisible = false;
AfterHide?.Invoke();
});
}
@@ -173,7 +167,7 @@ namespace Bit.App.Controls
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{
if (!LongPressAccountEnabled || !item.IsAccount)
if (!item.IsAccount)
{
return;
}

View File

@@ -45,28 +45,23 @@ namespace Bit.App.Controls
public ICommand LongPressAccountCommand { get; }
public bool FromIOSExtension { get; set; }
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
if (!item.AccountView.IsAccount)
if (item.AccountView.IsAccount)
{
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
return;
}
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
if (FromIOSExtension)
if (!item.AccountView.IsActive)
{
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
}
}
else if (AllowActiveAccountSelection)
else
{
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
_messagingService.Send("addAccount");
}
}

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="pages:AuthenticatorPageListItem">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:MonoLabel
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
StyleClass="list-title, list-title-platform"
Text="{Binding TotpCodeFormatted, Mode=OneWay}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.Name}" />
<controls:IconLabel
Grid.Column="1"
Grid.Row="1"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" />
</Grid>
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Margin="0, 0, 10, 0"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
Padding="0,0,1,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" />
</controls:ExtendedGrid>

View File

@@ -0,0 +1,121 @@
using System;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AuthenticatorViewCell : ExtendedGrid
{
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
//public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
// nameof(ButtonCommand), typeof(Command<CipherView>), typeof(AuthenticatorViewCell));
public AuthenticatorViewCell()
{
InitializeComponent();
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => GetValue(CipherProperty) as CipherView;
set => SetValue(CipherProperty, value);
}
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
}
public long TotpSec
{
get => (long)GetValue(TotpSecProperty);
set => SetValue(TotpSecProperty, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled ?? false
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
private string _iconImageSource = string.Empty;
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
private string _totpCodeFormatted = "938 928";
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => _totpCodeFormatted = value;
}
//public Command<CipherView> ButtonCommand
//{
// get => GetValue(ButtonCommandProperty) as Command<CipherView>;
// set => SetValue(ButtonCommandProperty, value);
//}
//protected override void OnPropertyChanged(string propertyName = null)
//{
// base.OnPropertyChanged(propertyName);
// if (propertyName == CipherProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// _cipherLabel.Text = Cipher.Name;
// }
// else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// ((AuthenticatorViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
// }
// else if (propertyName == TotpSecProperty.PropertyName)
// {
// if (Cipher == null)
// {
// return;
// }
// ((AuthenticatorViewCellViewModel)BindingContext).UpdateTotpSec(TotpSec);
// }
//}
private void MoreButton_Clicked(object sender, EventArgs e)
{
var cipher = ((sender as MiButton)?.BindingContext as AuthenticatorViewCellViewModel)?.Cipher;
if (cipher != null)
{
//ButtonCommand?.Execute(cipher);
}
}
}
}

View File

@@ -0,0 +1,103 @@
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AuthenticatorViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
private string _totpCodeFormatted = "938 928";
private string _totpSec;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public AuthenticatorViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public void UpdateTotpSec(long totpSec)
{
_totpSec = totpSec.ToString();
}
//private async Task TotpUpdateCodeAsync()
//{
// if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
// {
// _totpInterval = null;
// return;
// }
// _totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
// if (_totpCode != null)
// {
// if (_totpCode.Length > 4)
// {
// var half = (int)Math.Floor(_totpCode.Length / 2M);
// TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
// _totpCode.Substring(half));
// }
// else
// {
// TotpCodeFormatted = _totpCode;
// }
// }
// else
// {
// TotpCodeFormatted = null;
// _totpInterval = null;
// }
//}
}
}

View File

@@ -4,6 +4,5 @@ namespace Bit.App.Controls
{
public class ExtendedCollectionView : CollectionView
{
public string ExtraDataForLogging { get; set; }
}
}

View File

@@ -81,12 +81,10 @@ namespace Bit.App.Pages
}
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
string ssoToken;
try
{
var response = await _apiService.PreValidateSso(OrgIdentifier);
ssoToken = response.Token;
await _apiService.PreValidateSso(OrgIdentifier);
}
catch (ApiException e)
{
@@ -114,8 +112,7 @@ namespace Bit.App.Pages
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
"ssoToken=" + Uri.EscapeDataString(ssoToken);
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
WebAuthenticatorResult authResult = null;
try

View File

@@ -219,8 +219,7 @@ namespace Bit.App.Pages
// Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{
ResetPasswordKey = encryptedKey.EncryptedString,
MasterPasswordHash = masterPasswordHash,
ResetPasswordKey = encryptedKey.EncryptedString
};
var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user

View File

@@ -0,0 +1,97 @@
<?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.AuthenticatorPage"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:AuthenticatorPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
<ContentPage.BindingContext>
<pages:AuthenticatorPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<ToolbarItem x:Name="_aboutIconItem" x:Key="aboutIconItem" Icon="info.png"
Clicked="About_Clicked" Order="Primary" Priority="-1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AboutSend}" />
<ToolbarItem x:Name="_syncItem" x:Key="syncItem" Text="{u:I18n Sync}"
Clicked="Sync_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_lockItem" x:Key="lockItem" Text="{u:I18n Lock}"
Clicked="Lock_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_aboutTextItem" x:Key="aboutTextItem" Text="{u:I18n AboutSend}"
Clicked="About_Clicked" Order="Secondary" />
<ToolbarItem x:Name="_addItem" x:Key="addItem" Icon="plus.png"
Clicked="AddButton_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AddItem}" />
<DataTemplate x:Key="authenticatorTemplate"
x:DataType="pages:AuthenticatorPageListItem">
<controls:AuthenticatorViewCell
Cipher="{Binding Cipher}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
TotpSec="{Binding TotpSec}"/>
</DataTemplate>
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
<RefreshView>
<controls:ExtendedCollectionView
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<controls:ExtendedCollectionView.ItemTemplate>
<DataTemplate x:DataType="pages:AuthenticatorPageListItem">
<controls:AuthenticatorViewCell />
</DataTemplate>
</controls:ExtendedCollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</RefreshView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1" />
<Button
x:Name="_fab"
Image="plus.png"
Clicked="AddButton_Clicked"
Style="{StaticResource btn-fab}"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AddItem}">
<Button.Effects>
<effects:FabShadowEffect />
</Button.Effects>
</Button>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,176 @@
using Bit.App.Resources;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
public partial class AuthenticatorPage : BaseContentPage
{
#region Members
private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private readonly ICipherService _cipherService;
private AuthenticatorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action<string> _selectAction;
private readonly TabsPage _tabsPage;
#endregion
public AuthenticatorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null)
{
//_tabsPage = tabsPage;
InitializeComponent();
//_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_vm = BindingContext as AuthenticatorPageViewModel;
//_vm.Page = this;
//_fromTabPage = fromTabPage;
//_selectAction = selectAction;
if (Device.RuntimePlatform == Device.iOS)
{
_absLayout.Children.Remove(_fab);
ToolbarItems.Add(_aboutIconItem);
ToolbarItems.Add(_addItem);
}
else
{
ToolbarItems.Add(_syncItem);
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_aboutTextItem);
}
}
public async Task InitAsync()
{
await _vm.LoadAsync();
}
protected async override void OnAppearing()
{
base.OnAppearing();
//if (!_fromTabPage)
//{
// await InitAsync();
//}
//_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
//{
// if (message.Command == "updatedTheme")
// {
// Device.BeginInvokeOnMainThread(() =>
// {
// //_vm.RedrawPassword();
// });
// }
//});
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
{
try
{
await _vm.LoadAsync();
}
catch (Exception e) when (e.Message.Contains("No key."))
{
await Task.Delay(1000);
await _vm.LoadAsync();
}
}
else
{
await Task.Delay(5000);
if (!_vm.Loaded)
{
await _vm.LoadAsync();
}
}
AdjustToolbar();
//await CheckAddRequest();
}, _mainContent);
}
private async void Search_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
// var page = new SendsPage(_vm.Filter, _vm.Type != null);
// await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void Sync_Clicked(object sender, EventArgs e)
{
// await _vm.SyncAsync();
}
private async void Lock_Clicked(object sender, EventArgs e)
{
// await _vaultTimeoutService.LockAsync(true, true);
}
private void About_Clicked(object sender, EventArgs e)
{
// _vm.ShowAbout();
}
private async void AddButton_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
// var page = new SendAddEditPage(null, null, _vm.Type);
// await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
}
private async void Copy_Clicked(object sender, EventArgs e)
{
//await _vm.CopyAsync();
}
private async void More_Clicked(object sender, EventArgs e)
{
//if (!DoOnce())
//{
// return;
//}
//var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
// null, AppResources.PasswordHistory);
//if (selection == AppResources.PasswordHistory)
//{
// var page = new GeneratorHistoryPage();
// await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
//}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
//_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
private void AdjustToolbar()
{
//_addItem.IsEnabled = !_vm.Deleted;
//_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AuthenticatorPageListItem : ExtendedViewModel
{
//private string _totpCode;
private readonly ITotpService _totpService;
//public CipherView Cipher { get; set; }
//public CipherType? Type { get; set; }
//public int interval { get; set; }
//public long TotpSec { get; set; }
//private DateTime? _totpInterval = null;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public int interval { get; set; }
private string _totpSec;
private string _totpCode;
private string _totpCodeFormatted = "938 928";
public AuthenticatorPageListItem(CipherView cipherView, bool websiteIconsEnabled)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public async Task TotpTickAsync()
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % interval;
var totpSec = interval - mod;
TotpSec = totpSec.ToString();
//TotpLow = totpSec < 7;
if (mod == 0)
{
await TotpUpdateCodeAsync();
}
}
public async Task TotpUpdateCodeAsync()
{
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
{
if (_totpCode.Length > 4)
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
}
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Threading.Tasks;
using System.Linq;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AuthenticatorPageViewModel : BaseViewModel
{
#region Members
private readonly IClipboardService _clipboardService;
private readonly ITotpService _totpService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private bool _showList = true;
private bool _refreshing;
private bool _loaded;
private bool _websiteIconsEnabled = true;
//private long _totpSec;
#endregion
#region Ctor
public AuthenticatorPageViewModel()
{
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
PageTitle = AppResources.Authenticator;
Items = new ExtendedObservableCollection<AuthenticatorPageListItem>();
}
#endregion
#region Methods
public async Task CopyAsync()
{
//await _clipboardService.CopyTextAsync(Password);
//_platformUtilsService.ShowToast("success", null,
// string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
public async Task LoadAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
if (!authed)
{
return;
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
try
{
await LoadDataAsync();
}
finally
{
ShowList = true;
Refreshing = false;
}
}
private async Task LoadDataAsync()
{
var _allCiphers = await _cipherService.GetAllDecryptedAsync();
_allCiphers = _allCiphers.Where(c => c.Type == Core.Enums.CipherType.Login && c.Login.Totp != null).ToList();
var filteredCiphers = _allCiphers.Select(c => new AuthenticatorPageListItem(c, WebsiteIconsEnabled)).ToList();
Items.ResetWithRange(filteredCiphers);
foreach (AuthenticatorPageListItem item in Items)
{
item.TotpUpdateCodeAsync();
}
//await TotpUpdateCodeAsync();
// var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
// await TotpTickAsync(interval);
// _totpInterval = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
foreach(AuthenticatorPageListItem item in Items)
{
item.TotpTickAsync();
}
return true;
});
//}
//private async Task TotpTickAsync(int intervalSeconds)
//{
// var epoc = CoreHelpers.EpocUtcNow() / 1000;
// var mod = epoc % intervalSeconds;
// var totpSec = intervalSeconds - mod;
// TotpSec = totpSec.ToString();
// TotpLow = totpSec < 7;
// if (mod == 0)
// {
// await TotpUpdateCodeAsync();
// }
}
#endregion
#region Properties
public ExtendedObservableCollection<AuthenticatorPageListItem> Items { get; set; }
public Command RefreshCommand { get; set; }
public bool ShowList
{
get => _showList;
set => SetProperty(ref _showList, value);
}
public bool Refreshing
{
get => _refreshing;
set => SetProperty(ref _refreshing, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool Loaded
{
get => _loaded;
set => SetProperty(ref _loaded, value);
}
//public long TotpSec
//{
// get => _totpSec;
// set => SetProperty(ref _totpSec, value);
//}
#endregion
}
}

View File

@@ -48,8 +48,7 @@
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}"
VerticalOptions="FillAndExpand"
StyleClass="list, list-platform"
ExtraDataForLogging="Generator History Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
<Grid

View File

@@ -58,7 +58,8 @@ namespace Bit.App.Pages
private async void CopyAsync(GeneratedPasswordHistory ph)
{
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
public async Task UpdateOnThemeChanged()

View File

@@ -5,6 +5,7 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -318,7 +319,8 @@ namespace Bit.App.Pages
public async Task CopyAsync()
{
await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
_platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
private void LoadFromOptions()

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?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.SendGroupingsPage"
@@ -138,8 +138,7 @@
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Send Groupings Page" />
StyleClass="list, list-platform" />
</RefreshView>
</StackLayout>
</ResourceDictionary>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?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"
@@ -66,8 +66,7 @@
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Sends Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:SendView">
<controls:SendViewCell

View File

@@ -38,8 +38,7 @@
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Folders Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:FolderView">
<controls:ExtendedStackLayout

View File

@@ -113,7 +113,6 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Settings Page" />
StyleClass="list, list-platform" />
</pages:BaseContentPage>

View File

@@ -167,7 +167,7 @@ namespace Bit.App.Pages
{
await _vm.UpdatePinAsync();
}
else if (item.Name == AppResources.SubmitCrashLogs)
else if (item.Name == AppResources.ReportCrashLogs)
{
await _vm.LoggerReportingAsync();
}

View File

@@ -296,7 +296,7 @@ namespace Bit.App.Pages
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
};
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
{
@@ -525,7 +525,7 @@ namespace Bit.App.Pages
#if !FDROID
new SettingsPageListItem
{
Name = AppResources.SubmitCrashLogs,
Name = AppResources.ReportCrashLogs,
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
},
#endif

View File

@@ -18,6 +18,7 @@ namespace Bit.App.Pages
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private NavigationPage _groupingsPage;
private NavigationPage _authenticatorPage;
private NavigationPage _sendGroupingsPage;
private NavigationPage _generatorPage;
@@ -32,8 +33,16 @@ namespace Bit.App.Pages
Title = AppResources.MyVault,
IconImageSource = "lock.png"
};
Children.Add(_groupingsPage);
_authenticatorPage = new NavigationPage(new AuthenticatorPage(true, null, this))
{
Title = AppResources.Authenticator,
IconImageSource = "info.png"
};
Children.Add(_authenticatorPage);
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
{
Title = AppResources.Send,

View File

@@ -30,7 +30,6 @@ namespace Bit.App.Pages
CipherType? type = null,
string folderId = null,
string collectionId = null,
string organizationId = null,
string name = null,
string uri = null,
bool fromAutofill = false,
@@ -52,7 +51,6 @@ namespace Bit.App.Pages
_vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId;
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
_vm.OrganizationId = organizationId;
_vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri;

View File

@@ -85,8 +85,7 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Autofill Ciphers Page" />
StyleClass="list, list-platform" />
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -49,31 +49,6 @@
</ContentPage.Resources>
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
<StackLayout
IsVisible="{Binding ShowVaultFilter}"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,0,0">
<Label
Text="{Binding VaultFilterDescription}"
LineBreakMode="TailTruncation"
Margin="10,0"
StyleClass="text-md, text-muted"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button-text, list-row-button-platform"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
StyleClass="text-muted"
@@ -93,8 +68,7 @@
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Ciphers Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:CipherView">
<controls:CipherViewCell

View File

@@ -17,8 +17,7 @@ namespace Bit.App.Pages
private CiphersPageViewModel _vm;
private bool _hasFocused;
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
string autofillUrl = null, bool deleted = false)
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string autofillUrl = null, bool deleted = false)
{
InitializeComponent();
_vm = BindingContext as CiphersPageViewModel;
@@ -34,7 +33,6 @@ namespace Bit.App.Pages
{
_vm.PageTitle = AppResources.SearchVault;
}
_vm.VaultFilterDescription = vaultFilterSelection;
if (Device.RuntimePlatform == Device.iOS)
{

View File

@@ -5,17 +5,17 @@ using System.Threading;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class CiphersPageViewModel : VaultFilterViewModel
public class CiphersPageViewModel : BaseViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService;
@@ -23,10 +23,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
private CancellationTokenSource _searchCancellationTokenSource;
private readonly ILogger _logger;
private bool _showNoData;
private bool _showList;
@@ -40,9 +37,6 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
@@ -54,11 +48,6 @@ namespace Bit.App.Pages
public string AutofillUrl { get; set; }
public bool Deleted { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool ShowNoData
{
get => _showNoData;
@@ -87,9 +76,11 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
await InitVaultFilterAsync(true);
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
PerformSearchIfPopulated();
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);
}
}
public void Search(string searchText, int? timeout = null)
@@ -116,9 +107,8 @@ namespace Bit.App.Pages
}
try
{
var vaultFilteredCiphers = await GetAllCiphersAsync();
ciphers = await _searchService.SearchCiphersAsync(searchText,
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
@@ -202,19 +192,6 @@ namespace Bit.App.Pages
}
}
private void PerformSearchIfPopulated()
{
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);
}
}
protected override async Task OnVaultFilterSelectedAsync()
{
PerformSearchIfPopulated();
}
private async void CipherOptionsAsync(CipherView cipher)
{
if ((Page as BaseContentPage).DoOnce())

View File

@@ -123,7 +123,7 @@
AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button-text, list-row-button-platform"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
@@ -155,8 +155,7 @@
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Groupings Page" />
StyleClass="list, list-platform" />
</RefreshView>
</StackLayout>
</ResourceDictionary>

View File

@@ -263,7 +263,7 @@ namespace Bit.App.Pages
}
if (!_vm.Deleted && DoOnce())
{
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
@@ -16,7 +17,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public class GroupingsPageViewModel : VaultFilterViewModel
public class GroupingsPageViewModel : BaseViewModel
{
private const int NoFolderListSize = 100;
@@ -29,7 +30,10 @@ namespace Bit.App.Pages
private bool _showList;
private bool _websiteIconsEnabled;
private bool _syncRefreshing;
private bool _showVaultFilter;
private string _vaultFilterSelection;
private string _noDataText;
private List<Organization> _organizations;
private List<CipherView> _allCiphers;
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
@@ -74,6 +78,9 @@ namespace Bit.App.Pages
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
@@ -101,11 +108,6 @@ namespace Bit.App.Pages
public List<Core.Models.View.CollectionView> Collections { get; set; }
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool Refreshing
{
get => _refreshing;
@@ -151,12 +153,30 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowVaultFilter
{
get => _showVaultFilter;
set => SetProperty(ref _showVaultFilter, value);
}
public string VaultFilterDescription
{
get
{
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
{
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
}
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
}
set => SetProperty(ref _vaultFilterSelection, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
public ICommand VaultFilterCommand { get; }
public bool LoadedOnce { get; set; }
public async Task LoadAsync()
@@ -181,9 +201,14 @@ namespace Bit.App.Pages
return;
}
await InitVaultFilterAsync(MainPage);
_organizations = await _organizationService.GetAllAsync();
if (MainPage)
{
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
@@ -369,8 +394,22 @@ namespace Bit.App.Pages
SyncRefreshing = false;
}
protected override async Task OnVaultFilterSelectedAsync()
public async Task VaultFilterOptionsAsync()
{
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
if (_organizations.Any())
{
options.AddRange(_organizations.Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
await LoadAsync();
}
@@ -457,8 +496,8 @@ namespace Bit.App.Pages
private async Task LoadDataAsync()
{
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
NoDataText = AppResources.NoItems;
_allCiphers = await GetAllCiphersAsync();
HasCiphers = _allCiphers.Any();
FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear();
@@ -472,7 +511,7 @@ namespace Bit.App.Pages
if (MainPage)
{
await FillFoldersAndCollectionsAsync();
await FillFoldersAndCollectionsAsync(orgId);
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
@@ -596,9 +635,28 @@ namespace Bit.App.Pages
}
}
private async Task FillFoldersAndCollectionsAsync()
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
{
string orgId = null;
var decCiphers = await _cipherService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
}
else if (IsVaultFilterOrgVault)
{
orgId = GetVaultFilterOrgId();
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
}
else
{
_allCiphers = decCiphers;
}
return orgId;
}
private async Task FillFoldersAndCollectionsAsync(string orgId)
{
var orgId = GetVaultFilterOrgId();
var decFolders = await _folderService.GetAllDecryptedAsync();
var decCollections = await _collectionService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
@@ -618,6 +676,16 @@ namespace Bit.App.Pages
}
}
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
_vaultFilterSelection != AppResources.MyVault;
private string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
private List<FolderView> BuildFolders(List<FolderView> decFolders)
{
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();

View File

@@ -39,8 +39,7 @@
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}"
VerticalOptions="FillAndExpand"
StyleClass="list, list-platform"
ExtraDataForLogging="Password History Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:PasswordHistoryView">
<Grid

View File

@@ -51,7 +51,8 @@ namespace Bit.App.Pages
private async void CopyAsync(PasswordHistoryView ph)
{
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
}
}

View File

@@ -45,8 +45,6 @@ namespace Bit.App.Pages
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
var autofocusCts = _autofocusCts;
// this task is needed to be awaited OnDisappearing to avoid some crashes
// when changing the value of _zxing.IsScanning
_continuousAutofocusTask = Task.Run(async () =>
{
try
@@ -54,7 +52,7 @@ namespace Bit.App.Pages
while (!autofocusCts.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
await Device.InvokeOnMainThreadAsync(() =>
Device.BeginInvokeOnMainThread(() =>
{
if (!autofocusCts.IsCancellationRequested)
{
@@ -75,10 +73,7 @@ namespace Bit.App.Pages
{
_autofocusCts?.Cancel();
if (_continuousAutofocusTask != null)
{
await _continuousAutofocusTask;
}
await _continuousAutofocusTask;
_zxing.IsScanning = false;
base.OnDisappearing();

View File

@@ -663,7 +663,7 @@ namespace Bit.App.Pages
await _clipboardService.CopyTextAsync(text);
if (!string.IsNullOrWhiteSpace(name))
{
_platformUtilsService.ShowToastForCopiedValue(name);
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
}
if (id == "LoginPassword")
{

View File

@@ -1,123 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages
{
public abstract class VaultFilterViewModel : BaseViewModel
{
protected abstract ICipherService cipherService { get; }
protected abstract IPolicyService policyService { get; }
protected abstract IOrganizationService organizationService { get; }
protected abstract ILogger logger { get; }
protected bool _showVaultFilter;
protected bool _personalOwnershipPolicyApplies;
protected string _vaultFilterSelection;
protected List<Organization> _organizations;
public VaultFilterViewModel()
{
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}
public ICommand VaultFilterCommand { get; set; }
public bool ShowVaultFilter
{
get => _showVaultFilter;
set => SetProperty(ref _showVaultFilter, value);
}
public string VaultFilterDescription
{
get
{
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
{
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
}
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
}
set => SetProperty(ref _vaultFilterSelection, value);
}
public string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
_vaultFilterSelection != AppResources.MyVault;
protected async Task InitVaultFilterAsync(bool shouldUpdateShowVaultFilter)
{
_organizations = await organizationService.GetAllAsync();
if (_organizations?.Any() ?? false)
{
_personalOwnershipPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
var singleOrgPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.OnlyOrg);
if (_vaultFilterSelection == null || (_personalOwnershipPolicyApplies && singleOrgPolicyApplies))
{
VaultFilterDescription = AppResources.AllVaults;
}
}
if (shouldUpdateShowVaultFilter)
{
await Task.Delay(100);
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
}
}
protected async Task<List<CipherView>> GetAllCiphersAsync()
{
var decCiphers = await cipherService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
return decCiphers.Where(c => c.OrganizationId == null).ToList();
}
if (IsVaultFilterOrgVault)
{
var orgId = GetVaultFilterOrgId();
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
}
return decCiphers;
}
protected async Task VaultFilterOptionsAsync()
{
var options = new List<string> { AppResources.AllVaults };
if (!_personalOwnershipPolicyApplies)
{
options.Add(AppResources.MyVault);
}
if (_organizations.Any())
{
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
await OnVaultFilterSelectedAsync();
}
protected abstract Task OnVaultFilterSelectedAsync();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -299,6 +299,10 @@
<value>My Vault</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
<comment>Label for an entity name.</comment>
@@ -2196,11 +2200,11 @@
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Enter the verification code that was sent to your email</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Submit crash logs</value>
<data name="ReportCrashLogs" xml:space="preserve">
<value>Report crash logs</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
<data name="ReportCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by allowing crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>

View File

@@ -20,7 +20,6 @@ namespace Bit.App.Services
private const int DialogPromiseExpiration = 600000; // 10 minutes
private readonly IDeviceActionService _deviceActionService;
private readonly IClipboardService _clipboardService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
@@ -29,12 +28,10 @@ namespace Bit.App.Services
public MobilePlatformUtilsService(
IDeviceActionService deviceActionService,
IClipboardService clipboardService,
IMessagingService messagingService,
IBroadcasterService broadcasterService)
{
_deviceActionService = deviceActionService;
_clipboardService = clipboardService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
}
@@ -132,15 +129,6 @@ namespace Bit.App.Services
return true;
}
public void ShowToastForCopiedValue(string valueNameCopied)
{
if (!_clipboardService.IsCopyNotificationHandledByPlatform())
{
ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, valueNameCopied));
}
}
public bool SupportsFido2()
{
return _deviceActionService.SupportsFido2();

View File

@@ -25,7 +25,6 @@ namespace Bit.App.Services
Constants.LastBuildKey,
Constants.ClearCiphersCacheKey,
Constants.BiometricIntegrityKey,
Constants.iOSExtensionActiveUserIdKey,
Constants.iOSAutoFillClearCiphersCacheKey,
Constants.iOSAutoFillBiometricIntegrityKey,
Constants.iOSExtensionClearCiphersCacheKey,
@@ -33,7 +32,7 @@ namespace Bit.App.Services
Constants.iOSShareExtensionClearCiphersCacheKey,
Constants.iOSShareExtensionBiometricIntegrityKey,
Constants.RememberedEmailKey,
Constants.RememberedOrgIdentifierKey
Constants.RememberedOrgIdentifierKey,
};
public MobileStorageService(

View File

@@ -268,16 +268,6 @@
<Setter Property="TextColor"
Value="{DynamicResource ButtonColor}" />
</Style>
<Style TargetType="Button"
ApplyToDerivedTypes="True"
Class="list-row-button-text">
<Setter Property="BackgroundColor"
Value="Transparent" />
<Setter Property="Padding"
Value="0" />
<Setter Property="TextColor"
Value="{DynamicResource ButtonTextColor}" />
</Style>
<Style TargetType="Button"
ApplyToDerivedTypes="True"
Class="segmented-button">

View File

@@ -1,217 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Utilities.AccountManagement
{
public class AccountsManager : IAccountsManager
{
private readonly IBroadcasterService _broadcasterService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _secureStorageService;
private readonly IStateService _stateService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
Func<AppOptions> _getOptionsFunc;
private IAccountsManagerHost _accountsManagerHost;
public AccountsManager(IBroadcasterService broadcasterService,
IVaultTimeoutService vaultTimeoutService,
IStorageService secureStorageService,
IStateService stateService,
IPlatformUtilsService platformUtilsService,
IAuthService authService)
{
_broadcasterService = broadcasterService;
_vaultTimeoutService = vaultTimeoutService;
_secureStorageService = secureStorageService;
_stateService = stateService;
_platformUtilsService = platformUtilsService;
_authService = authService;
}
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
public void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost)
{
_getOptionsFunc = getOptionsFunc;
_accountsManagerHost = accountsManagerHost;
_broadcasterService.Subscribe(nameof(AccountsManager), OnMessage);
}
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
{
// TODO: this could be improved by doing chain of responsability pattern
// but for now it may be an overkill, if logic gets more complex consider refactoring it
var authed = isAuthed ?? await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
_accountsManagerHost.Navigate(NavigationTarget.Lock);
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
_accountsManagerHost.Navigate(NavigationTarget.AddEditCipher);
}
else if (Options.Uri != null)
{
_accountsManagerHost.Navigate(NavigationTarget.AutofillCiphers);
}
else if (Options.CreateSend != null)
{
_accountsManagerHost.Navigate(NavigationTarget.SendAddEdit);
}
else
{
_accountsManagerHost.Navigate(NavigationTarget.Home);
}
}
else
{
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
}
else
{
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
}
}
}
private async void OnMessage(Message message)
{
switch (message.Command)
{
case AccountsManagerMessageCommands.LOCKED:
Locked(message.Data as Tuple<string, bool>);
break;
case AccountsManagerMessageCommands.LOCK_VAULT:
await _vaultTimeoutService.LockAsync(true);
break;
case AccountsManagerMessageCommands.LOGOUT:
LogOut(message.Data as Tuple<string, bool, bool>);
break;
case AccountsManagerMessageCommands.LOGGED_OUT:
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
break;
case AccountsManagerMessageCommands.ADD_ACCOUNT:
AddAccount();
break;
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
await _accountsManagerHost.UpdateThemeAsync();
break;
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
await SwitchedAccountAsync();
break;
}
}
private void Locked(Tuple<string, bool> extras)
{
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
private async Task LockedAsync(string userId, bool userInitiated)
{
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
await _accountsManagerHost.SetPreviousPageInfoAsync();
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
}
private void AddAccount()
{
Device.BeginInvokeOnMainThread(() =>
{
Options.HideAccountSwitcher = false;
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
});
}
private void LogOut(Tuple<string, bool, bool> extras)
{
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync(userId, userInitiated);
await NavigateOnAccountChangeAsync();
_authService.LogOut(() =>
{
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
await Device.InvokeOnMainThreadAsync(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await NavigateOnAccountChangeAsync();
}
await Task.Delay(50);
await _accountsManagerHost.UpdateThemeAsync();
});
}
}
}

View File

@@ -1,14 +0,0 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class LockNavigationParams : INavigationParams
{
public LockNavigationParams(bool autoPromptBiometric = true)
{
AutoPromptBiometric = autoPromptBiometric;
}
public bool AutoPromptBiometric { get; }
}
}

View File

@@ -1,14 +0,0 @@
using Bit.App.Abstractions;
namespace Bit.App.Utilities.AccountManagement
{
public class LoginNavigationParams : INavigationParams
{
public LoginNavigationParams(string email)
{
Email = email;
}
public string Email { get; }
}
}

View File

@@ -97,14 +97,16 @@ namespace Bit.App.Utilities
else if (selection == AppResources.CopyUsername)
{
await clipboardService.CopyTextAsync(cipher.Login.Username);
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
}
else if (selection == AppResources.CopyPassword)
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(cipher.Login.Password);
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
}
}
@@ -117,7 +119,8 @@ namespace Bit.App.Utilities
if (!string.IsNullOrWhiteSpace(totp))
{
await clipboardService.CopyTextAsync(totp);
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
}
}
}
@@ -130,7 +133,8 @@ namespace Bit.App.Utilities
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(cipher.Card.Number);
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
}
}
else if (selection == AppResources.CopySecurityCode)
@@ -138,14 +142,16 @@ namespace Bit.App.Utilities
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await clipboardService.CopyTextAsync(cipher.Card.Code);
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
}
}
else if (selection == AppResources.CopyNotes)
{
await clipboardService.CopyTextAsync(cipher.Notes);
platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
}
return selection;
}
@@ -256,7 +262,8 @@ namespace Bit.App.Utilities
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
await clipboardService.CopyTextAsync(GetSendUrl(send));
platformUtilsService.ShowToastForCopiedValue(AppResources.SendLink);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
}
public static async Task ShareSendUrlAsync(SendView send)

View File

@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id);
Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<object> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
void SetUrls(EnvironmentUrls urls);

View File

@@ -8,17 +8,9 @@ namespace Bit.Core.Abstractions
/// Copies the <paramref name="text"/> to the Clipboard.
/// If <paramref name="expiresInMs"/> is set > 0 then the Clipboard will be cleared after this time in milliseconds.
/// if less than 0 then it takes the configuration that the user set in Options.
/// If <paramref name="isSensitive"/> is true the sensitive flag is passed to the clipdata to obfuscate the
/// clipboard text in the popup (Android 13+ only)
/// </summary>
/// <param name="text">Text to be copied to the Clipboard</param>
/// <param name="expiresInMs">Expiration time in milliseconds of the copied text</param>
/// <param name="isSensitive">Flag to mark copied text as sensitive</param>
Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true);
/// <summary>
/// Returns true if the platform provides its own notification when text is copied to the clipboard
/// </summary>
bool IsCopyNotificationHandledByPlatform();
Task CopyTextAsync(string text, int expiresInMs = -1);
}
}

View File

@@ -23,7 +23,6 @@ namespace Bit.Core.Abstractions
Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator);
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
void ShowToastForCopiedValue(string valueNameCopied);
bool SupportsFido2();
bool SupportsDuo();
Task<bool> SupportsBiometricAsync();

View File

@@ -14,7 +14,6 @@ namespace Bit.Core.Abstractions
Task<string> GetActiveUserIdAsync();
Task<bool> IsActiveAccountAsync(string userId = null);
Task SetActiveUserAsync(string userId);
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
Task<bool> IsAuthenticatedAsync(string userId = null);
Task<string> GetUserIdAsync(string email);
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
@@ -146,6 +145,5 @@ namespace Bit.Core.Abstractions
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
Task<string> GetTwoFactorTokenAsync(string email = null);
Task SetTwoFactorTokenAsync(string value, string email = null);
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
}
}

View File

@@ -25,7 +25,6 @@
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
public static string EventCollectionKey = "eventCollection";
public static string RememberedEmailKey = "rememberedEmail";
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";

View File

@@ -1,13 +0,0 @@
namespace Bit.Core.Enums
{
public enum NavigationTarget
{
HomeLogin,
Login,
Lock,
Home,
AddEditCipher,
AutofillCiphers,
SendAddEdit
}
}

View File

@@ -2,7 +2,6 @@
{
public class OrganizationUserResetPasswordEnrollmentRequest
{
public string MasterPasswordHash { get; set; }
public string ResetPasswordKey { get; set; }
}
}

View File

@@ -1,7 +0,0 @@
namespace Bit.Core.Models.Response
{
public class SsoPrevalidateResponse
{
public string Token { get; set; }
}
}

View File

@@ -547,7 +547,7 @@ namespace Bit.Core.Services
return accessToken;
}
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
public async Task<object> PreValidateSso(string identifier)
{
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
using (var requestMessage = new HttpRequestMessage())
@@ -571,8 +571,7 @@ namespace Bit.Core.Services
var error = await HandleErrorAsync(response, false, true);
throw new ApiException(error);
}
var responseJsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
return null;
}
}

View File

@@ -249,14 +249,9 @@ namespace Bit.Core.Services
public async Task<bool> ShouldShowVaultFilterAsync()
{
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
{
return false;
}
var organizations = await _organizationService.GetAllAsync();
return organizations?.Any() ?? false;
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
}
private bool? GetPolicyBool(Policy policy, string key)

View File

@@ -15,20 +15,16 @@ namespace Bit.Core.Services
{
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService;
private readonly IMessagingService _messagingService;
private State _state;
private bool _migrationChecked;
public List<AccountView> AccountViews { get; set; }
public StateService(IStorageService storageService,
IStorageService secureStorageService,
IMessagingService messagingService)
public StateService(IStorageService storageService, IStorageService secureStorageService)
{
_storageService = storageService;
_secureStorageService = secureStorageService;
_messagingService = messagingService;
}
public async Task<string> GetActiveUserIdAsync()
@@ -71,28 +67,6 @@ namespace Bit.Core.Services
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
}
public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync()
{
var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync();
if (string.IsNullOrEmpty(extensionUserId))
{
return;
}
if (_state?.ActiveUserId == extensionUserId)
{
// Clear the value in case the user changes the active user from the app
// so if that happens and the user sends the app to background and comes back
// the user is not changed again.
await SaveExtensionActiveUserIdToStorageAsync(null);
return;
}
await SetActiveUserAsync(extensionUserId);
await SaveExtensionActiveUserIdToStorageAsync(null);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
public async Task<bool> IsAuthenticatedAsync(string userId = null)
{
return await GetAccessTokenAsync(userId) != null;
@@ -1536,16 +1510,6 @@ namespace Bit.Core.Services
await _storageService.SaveAsync(Constants.StateKey, state);
}
private async Task<string> GetExtensionActiveUserIdFromStorageAsync()
{
return await _storageService.GetAsync<string>(Constants.iOSExtensionActiveUserIdKey);
}
public async Task SaveExtensionActiveUserIdToStorageAsync(string userId)
{
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
}
private async Task CheckStateAsync()
{
if (!_migrationChecked)

View File

@@ -1,13 +0,0 @@
namespace Bit.Core.Utilities
{
public static class AccountsManagerMessageCommands
{
public const string LOCKED = "locked";
public const string LOCK_VAULT = "lockVault";
public const string LOGOUT = "logout";
public const string LOGGED_OUT = "loggedOut";
public const string ADD_ACCOUNT = "addAccount";
public const string ACCOUNT_ADDED = "accountAdded";
public const string SWITCHED_ACCOUNT = "switchedAccount";
}
}

View File

@@ -1,32 +1,27 @@
using System;
using System.Threading.Tasks;
using AuthenticationServices;
using AuthenticationServices;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using CoreNFC;
using Foundation;
using System;
using System.Threading.Tasks;
using Bit.App.Pages;
using UIKit;
using Xamarin.Forms;
using Bit.App.Utilities;
using Bit.App.Models;
using Bit.iOS.Core.Views;
using CoreNFC;
namespace Bit.iOS.Autofill
{
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
public partial class CredentialProviderViewController : ASCredentialProviderViewController
{
private Context _context;
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
private IAccountsManager _accountsManager;
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
public CredentialProviderViewController(IntPtr handle)
: base(handle)
@@ -61,7 +56,7 @@ namespace Bit.iOS.Autofill
}
if (!await IsAuthed())
{
await _accountsManager.NavigateOnAccountChangeAsync(false);
LaunchHomePage();
}
else if (await IsLocked())
{
@@ -83,8 +78,9 @@ namespace Bit.iOS.Autofill
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
{
InitAppIfNeeded();
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
await stateService.SetPasswordRepromptAutofillAsync(false);
await stateService.SetPasswordVerifiedAutofillAsync(false);
if (!await IsAuthed() || await IsLocked())
{
var err = new NSError(new NSString("ASExtensionErrorDomain"),
@@ -101,7 +97,7 @@ namespace Bit.iOS.Autofill
InitAppIfNeeded();
if (!await IsAuthed())
{
await _accountsManager.NavigateOnAccountChangeAsync(false);
LaunchHomePage();
return;
}
_context.CredentialIdentity = credentialIdentity;
@@ -114,7 +110,7 @@ namespace Bit.iOS.Autofill
_context.Configuring = true;
if (!await IsAuthed())
{
await _accountsManager.NavigateOnAccountChangeAsync(false);
LaunchHomePage();
return;
}
CheckLock(() => PerformSegue("setupSegue", this));
@@ -233,6 +229,7 @@ namespace Bit.iOS.Autofill
return;
}
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var decCipher = await cipher.DecryptAsync();
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
{
@@ -240,13 +237,13 @@ namespace Bit.iOS.Autofill
// already verified the password.
if (!userInteraction)
{
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
await stateService.SetPasswordRepromptAutofillAsync(true);
var err = new NSError(new NSString("ASExtensionErrorDomain"),
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
ExtensionContext?.CancelRequest(err);
return;
}
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
else if (!await stateService.GetPasswordVerifiedAutofillAsync())
{
// Add a timeout to resolve keyboard not always showing up.
await Task.Delay(250);
@@ -261,10 +258,10 @@ namespace Bit.iOS.Autofill
}
}
string totpCode = null;
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync();
if (!disableTotpCopy.GetValueOrDefault(false))
{
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync();
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
{
@@ -278,7 +275,8 @@ namespace Bit.iOS.Autofill
private async void CheckLock(Action notLockedAction)
{
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync())
{
PerformSegue("lockPasswordSegue", this);
}
@@ -296,7 +294,8 @@ namespace Bit.iOS.Autofill
private Task<bool> IsAuthed()
{
return _stateService.Value.IsAuthenticatedAsync();
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
return stateService.IsAuthenticatedAsync();
}
private void LogoutIfAuthed()
@@ -305,7 +304,8 @@ namespace Bit.iOS.Autofill
{
if (await IsAuthed())
{
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
if (deviceActionService.SystemMajorVersion() >= 12)
{
@@ -331,16 +331,12 @@ namespace Bit.iOS.Autofill
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
iOSCoreHelpers.InitLogger();
iOSCoreHelpers.Bootstrap();
var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions);
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
iOSCoreHelpers.AppearanceAdjustments();
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => appOptions, this);
}
private void InitAppIfNeeded()
@@ -518,35 +514,5 @@ namespace Bit.iOS.Autofill
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(updateTempPasswordController, true, null);
}
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
public Task UpdateThemeAsync() => Task.CompletedTask;
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
DismissViewController(false, () => LaunchHomePage());
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
DismissViewController(false, () => LaunchLoginFlow(loginParams.Email));
}
else
{
DismissViewController(false, () => LaunchLoginFlow());
}
break;
case NavigationTarget.Lock:
DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
break;
case NavigationTarget.AutofillCiphers:
case NavigationTarget.Home:
DismissViewController(false, () => PerformSegue("loginListSegue", this));
break;
}
}
}
}

View File

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

View File

@@ -1,17 +1,10 @@
using System;
using Bit.App.Controls;
using Bit.iOS.Core.Utilities;
using UIKit;
namespace Bit.iOS.Autofill
{
public partial class LockPasswordViewController : Core.Controllers.BaseLockPasswordViewController
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController
{
AccountSwitchingOverlayView _accountSwitchingOverlayView;
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
public override UITableView TableView => MainTableView;
public LockPasswordViewController(IntPtr handle)
: base(handle)
{
@@ -27,21 +20,6 @@ namespace Bit.iOS.Autofill
public override Action Success => () => CPViewController.DismissLockAndContinue();
public override Action Cancel => () => CPViewController.CompleteRequest();
public override async void ViewDidLoad()
{
base.ViewDidLoad();
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
{
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
}
partial void SubmitButton_Activated(UIBarButtonItem sender)
{
var task = CheckPasswordAsync();

View File

@@ -1,79 +1,64 @@
// WARNING
//
// This file has been generated automatically by Visual Studio to store outlets and
// actions made in the UI designer. If it is removed, they will be lost.
// Manual changes to this file may not be handled correctly.
// This file has been generated automatically by Visual Studio from the outlets and
// actions declared in your storyboard file.
// Manual changes to this file will not be maintained.
//
using Foundation;
using System;
using System.CodeDom.Compiler;
using UIKit;
namespace Bit.iOS.Autofill
{
[Register ("LockPasswordViewController")]
partial class LockPasswordViewController
{
[Outlet]
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
[Register ("LockPasswordViewController")]
partial class LockPasswordViewController
{
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem CancelButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem CancelButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UITableView MainTableView { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UITableView MainTableView { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem SubmitButton { get; set; }
[Outlet]
UIKit.UIView OverlayView { get; set; }
[Action ("CancelButton_Activated:")]
[GeneratedCode ("iOS Designer", "1.0")]
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem SubmitButton { get; set; }
[Action ("SubmitButton_Activated:")]
[GeneratedCode ("iOS Designer", "1.0")]
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
[Action ("AccountSwitchingBarButton_Activated:")]
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
void ReleaseDesignerOutlets ()
{
if (CancelButton != null) {
CancelButton.Dispose ();
CancelButton = null;
}
[Action ("CancelButton_Activated:")]
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
if (MainTableView != null) {
MainTableView.Dispose ();
MainTableView = null;
}
[Action ("SubmitButton_Activated:")]
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
void ReleaseDesignerOutlets ()
{
if (AccountSwitchingBarButton != null) {
AccountSwitchingBarButton.Dispose ();
AccountSwitchingBarButton = null;
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
if (CancelButton != null) {
CancelButton.Dispose ();
CancelButton = null;
}
if (MainTableView != null) {
MainTableView.Dispose ();
MainTableView = null;
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
if (SubmitButton != null) {
SubmitButton.Dispose ();
SubmitButton = null;
}
if (OverlayView != null) {
OverlayView.Dispose ();
OverlayView = null;
}
}
}
}
if (SubmitButton != null) {
SubmitButton.Dispose ();
SubmitButton = null;
}
}
}
}

View File

@@ -1,21 +1,19 @@
using System;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models;
using Bit.iOS.Autofill.Utilities;
using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using CoreFoundation;
using Foundation;
using UIKit;
using Bit.iOS.Core.Controllers;
using Bit.App.Resources;
using Bit.iOS.Core.Views;
using Bit.iOS.Autofill.Utilities;
using Bit.iOS.Core.Utilities;
using Bit.Core.Utilities;
using Bit.Core.Abstractions;
using Bit.App.Abstractions;
namespace Bit.iOS.Autofill
{
public partial class LoginListViewController : ExtendedUIViewController
public partial class LoginListViewController : ExtendedUITableViewController
{
public LoginListViewController(IntPtr handle)
: base(handle)
@@ -28,30 +26,17 @@ namespace Bit.iOS.Autofill
public CredentialProviderViewController CPViewController { get; set; }
public IPasswordRepromptService PasswordRepromptService { get; private set; }
AccountSwitchingOverlayView _accountSwitchingOverlayView;
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
LazyResolve<IBroadcasterService> _broadcasterService = new LazyResolve<IBroadcasterService>("broadcasterService");
LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
bool _alreadyLoadItemsOnce = false;
public async override void ViewDidLoad()
{
base.ViewDidLoad();
SubscribeSyncCompleted();
NavItem.Title = AppResources.Items;
CancelBarButton.Title = AppResources.Cancel;
TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 44;
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
TableView.Source = new TableSource(this);
await ((TableSource)TableView.Source).LoadItemsAsync();
_alreadyLoadItemsOnce = true;
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
@@ -59,16 +44,6 @@ namespace Bit.iOS.Autofill
{
await ASHelpers.ReplaceAllIdentities();
}
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
{
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
}
partial void CancelBarButton_Activated(UIBarButtonItem sender)
@@ -113,35 +88,6 @@ namespace Bit.iOS.Autofill
}
}
private void SubscribeSyncCompleted()
{
_broadcasterService.Value.Subscribe(nameof(LoginListViewController), message =>
{
if (message.Command == "syncCompleted" && _alreadyLoadItemsOnce)
{
DispatchQueue.MainQueue.DispatchAsync(async () =>
{
try
{
await ((TableSource)TableView.Source).LoadItemsAsync();
TableView.ReloadData();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
});
}
});
}
public override void ViewDidUnload()
{
base.ViewDidUnload();
_broadcasterService.Value.Unsubscribe(nameof(LoginListViewController));
}
public void DismissModal()
{
DismissViewController(true, async () =>
@@ -153,11 +99,13 @@ namespace Bit.iOS.Autofill
public class TableSource : ExtensionTableSource
{
private Context _context;
private LoginListViewController _controller;
public TableSource(LoginListViewController controller)
: base(controller.Context, controller)
{
_context = controller.Context;
_controller = controller;
}

View File

@@ -1,89 +1,59 @@
// WARNING
//
// This file has been generated automatically by Visual Studio to store outlets and
// actions made in the UI designer. If it is removed, they will be lost.
// Manual changes to this file may not be handled correctly.
// This file has been generated automatically by Visual Studio from the outlets and
// actions declared in your storyboard file.
// Manual changes to this file will not be maintained.
//
using Foundation;
using System;
using System.CodeDom.Compiler;
using UIKit;
namespace Bit.iOS.Autofill
{
[Register ("LoginListViewController")]
partial class LoginListViewController
{
[Outlet]
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
[Register ("LoginListViewController")]
partial class LoginListViewController
{
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem AddBarButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem AddBarButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem CancelBarButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UIBarButtonItem CancelBarButton { get; set; }
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; }
[Outlet]
UIKit.UIView MainView { get; set; }
[Action ("AddBarButton_Activated:")]
[GeneratedCode ("iOS Designer", "1.0")]
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UIKit.UINavigationItem NavItem { get; set; }
[Action ("CancelBarButton_Activated:")]
[GeneratedCode ("iOS Designer", "1.0")]
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
[Outlet]
UIKit.UIView OverlayView { get; set; }
[Action ("SearchBarButton_Activated:")]
[GeneratedCode ("iOS Designer", "1.0")]
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
[Outlet]
UIKit.UITableView TableView { get; set; }
void ReleaseDesignerOutlets ()
{
if (AddBarButton != null) {
AddBarButton.Dispose ();
AddBarButton = null;
}
[Action ("AccountSwitchingBarButton_Activated:")]
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
if (CancelBarButton != null) {
CancelBarButton.Dispose ();
CancelBarButton = null;
}
[Action ("AddBarButton_Activated:")]
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
[Action ("CancelBarButton_Activated:")]
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
[Action ("SearchBarButton_Activated:")]
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
void ReleaseDesignerOutlets ()
{
if (AddBarButton != null) {
AddBarButton.Dispose ();
AddBarButton = null;
}
if (CancelBarButton != null) {
CancelBarButton.Dispose ();
CancelBarButton = null;
}
if (MainView != null) {
MainView.Dispose ();
MainView = null;
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
if (OverlayView != null) {
OverlayView.Dispose ();
OverlayView = null;
}
if (TableView != null) {
TableView.Dispose ();
TableView = null;
}
if (AccountSwitchingBarButton != null) {
AccountSwitchingBarButton.Dispose ();
AccountSwitchingBarButton = null;
}
}
}
}
if (NavItem != null) {
NavItem.Dispose ();
NavItem = null;
}
}
}
}

View File

@@ -33,7 +33,10 @@ namespace Bit.iOS.Autofill
CancelBarButton.Title = AppResources.Cancel;
SearchBar.Placeholder = AppResources.Search;
SearchBar.BackgroundColor = SearchBar.BarTintColor = ThemeHelpers.ListHeaderBackgroundColor;
SearchBar.UpdateThemeIfNeeded();
if (!ThemeHelpers.LightTheme)
{
SearchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
}
TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 44;

View File

@@ -1,11 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="43">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="43">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -13,27 +9,30 @@
<scene sceneID="42">
<objects>
<viewController id="43" customClass="CredentialProviderViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="40"/>
<viewControllerLayoutGuide type="bottom" id="41"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="44">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="1713">
<rect key="frame" x="66" y="396" width="282" height="44"/>
<rect key="frame" x="66" y="316" width="282" height="44"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="YG6-2d-qpF"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="1713" firstAttribute="centerY" secondItem="44" secondAttribute="centerY" constant="-30" id="1763"/>
<constraint firstItem="1713" firstAttribute="centerX" secondItem="YG6-2d-qpF" secondAttribute="centerX" id="1764"/>
<constraint firstItem="1713" firstAttribute="centerX" secondItem="44" secondAttribute="centerX" id="1764"/>
</constraints>
</view>
<connections>
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
<segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/>
<segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/>
<segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/>
<segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/>
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -46,7 +45,7 @@
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="8A5-AR-QHS">
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -68,7 +67,7 @@
<objects>
<navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="1848">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
@@ -88,7 +87,7 @@
<objects>
<tableViewController id="2087" customClass="LoginAddViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" allowsSelection="NO" rowHeight="50" sectionHeaderHeight="22" sectionFooterHeight="22" id="2088">
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@@ -126,79 +125,50 @@
<!--Logins-->
<scene sceneID="2303">
<objects>
<viewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="q9o-3n-3xL">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<tableViewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="2305">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2305">
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
<rect key="frame" x="0.0" y="44.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
<rect key="frame" x="0.0" y="22" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="2304" id="2306"/>
<outlet property="delegate" destination="2304" id="2307"/>
</connections>
</tableView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tq0-Ep-tHr" userLabel="OverlayView">
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="BQW-dG-XMM"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="4wL-FF-CVk"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="Tq0-Ep-tHr" secondAttribute="trailing" id="5BV-0y-vU1"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="2305" secondAttribute="bottom" id="6EB-rh-lLS"/>
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="eT6-Bv-JaR"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="2305" secondAttribute="trailing" id="ofJ-fL-adF"/>
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="Tq0-Ep-tHr" secondAttribute="bottom" id="pBa-o1-Mtx"/>
<constraint firstItem="2305" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="pGe-1e-B4s"/>
<constraint firstItem="2305" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="xfQ-VQ-yWe"/>
</constraints>
</view>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="2304" id="2306"/>
<outlet property="delegate" destination="2304" id="2307"/>
</connections>
</tableView>
<toolbarItems/>
<navigationItem key="navigationItem" title="Logins" id="3734">
<leftBarButtonItems>
<barButtonItem title="Cancel" id="3735">
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
</connections>
</barButtonItem>
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="I0b-et-FGw" userLabel="Accoutn Switching Button">
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<action selector="AccountSwitchingBarButton_Activated:" destination="2304" id="dZn-bd-bC6"/>
</connections>
</barButtonItem>
</leftBarButtonItems>
<barButtonItem key="leftBarButtonItem" title="Cancel" id="3735">
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
</connections>
</barButtonItem>
<rightBarButtonItems>
<barButtonItem systemItem="add" id="3736">
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -216,17 +186,13 @@
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="AccountSwitchingBarButton" destination="I0b-et-FGw" id="KZj-EO-7wd"/>
<outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/>
<outlet property="CancelBarButton" destination="3735" id="name-outlet-3735"/>
<outlet property="MainView" destination="q9o-3n-3xL" id="gjJ-12-71Q"/>
<outlet property="NavItem" destination="3734" id="name-outlet-3734"/>
<outlet property="OverlayView" destination="Tq0-Ep-tHr" id="igj-R2-gXJ"/>
<outlet property="TableView" destination="2305" id="aUe-Uz-iIb"/>
<segue destination="1845" kind="presentation" identifier="loginAddSegue" modalPresentationStyle="fullScreen" modalTransitionStyle="coverVertical" id="3731"/>
<segue destination="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/>
</connections>
</viewController>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1157" y="566"/>
@@ -236,7 +202,7 @@
<objects>
<navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="4577">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -256,34 +222,37 @@
<scene sceneID="4579">
<objects>
<viewController id="4576" customClass="PasswordGeneratorViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="4571"/>
<viewControllerLayoutGuide type="bottom" id="4572"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="4930">
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<containerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4933">
<rect key="frame" x="0.0" y="90.5" width="414" height="683.5"/>
<rect key="frame" x="0.0" y="160.5" width="414" height="575.5"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<segue destination="4912" kind="embed" id="6480"/>
</connections>
</containerView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4940">
<rect key="frame" x="15" y="35" width="384" height="20.5"/>
<rect key="frame" x="15" y="105" width="384" height="20.5"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="darkTextColor"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="Kvt-lG-wyu"/>
<constraints>
<constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/>
<constraint firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/>
<constraint firstItem="4933" firstAttribute="top" secondItem="4940" secondAttribute="bottom" constant="35" id="6485"/>
<constraint firstItem="4933" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" id="6486"/>
<constraint firstItem="4940" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" constant="15" id="6487"/>
<constraint firstItem="4940" firstAttribute="top" secondItem="Kvt-lG-wyu" secondAttribute="top" constant="35" id="6488"/>
<constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
<constraint firstItem="Kvt-lG-wyu" firstAttribute="bottom" secondItem="4933" secondAttribute="bottom" id="6490"/>
<constraint firstItem="4933" firstAttribute="leading" secondItem="4930" secondAttribute="leading" id="6486"/>
<constraint firstItem="4940" firstAttribute="leading" secondItem="4930" secondAttribute="leading" constant="15" id="6487"/>
<constraint firstItem="4940" firstAttribute="top" secondItem="4571" secondAttribute="bottom" constant="35" id="6488"/>
<constraint firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
<constraint firstItem="4572" firstAttribute="top" secondItem="4933" secondAttribute="bottom" id="6490"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Generate Password" id="4580">
@@ -318,7 +287,7 @@
<objects>
<tableViewController id="4912" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="4913">
<rect key="frame" x="0.0" y="0.0" width="414" height="683.5"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="575.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
@@ -332,11 +301,15 @@
<point key="canvasLocation" x="4708" y="-194"/>
</scene>
<!--Navigation Controller-->
<!--Verify Fingerprint-->
<!--Verify PIN-->
<!--Navigation Controller-->
<!--Navigation Controller-->
<scene sceneID="6854">
<objects>
<navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="6857">
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -345,139 +318,88 @@
</textAttributes>
</navigationBar>
<connections>
<segue destination="cn5-F4-59n" kind="relationship" relationship="rootViewController" id="Q23-VB-h61"/>
<segue destination="7413" kind="relationship" relationship="rootViewController" id="8266"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="120" y="1407"/>
<point key="canvasLocation" x="390" y="1407"/>
</scene>
<!--Verify Master Password-->
<scene sceneID="5CE-bQ-Rfq">
<scene sceneID="7412">
<objects>
<viewController id="cn5-F4-59n" customClass="LockPasswordViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="0qM-RN-J2i" userLabel="Main View">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<tableViewController id="7413" customClass="LockPasswordViewController" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="7414">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="FcI-Ph-m9e">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="oQZ-wW-5uB">
<rect key="frame" x="0.0" y="49.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oQZ-wW-5uB" id="SUk-LD-cXo">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="1cB-yn-7ii">
<rect key="frame" x="0.0" y="93.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1cB-yn-7ii" id="xFt-Jc-feN">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Fzo-Cj-aM4">
<rect key="frame" x="0.0" y="137.5" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fzo-Cj-aM4" id="vSb-0G-Bic">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<sections/>
</tableView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sDX-BN-qLw" userLabel="OverlayView">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="uMZ-kT-NSt"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="FcI-Ph-m9e" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="68e-VU-o4s"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="FcI-Ph-m9e" secondAttribute="bottom" id="7UM-br-sxz"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="sDX-BN-qLw" secondAttribute="bottom" id="QGT-ck-8TL"/>
<constraint firstItem="sDX-BN-qLw" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="aVW-Nw-awb"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="sDX-BN-qLw" secondAttribute="trailing" id="f5n-9J-y3c"/>
<constraint firstItem="FcI-Ph-m9e" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="sKL-iw-CWd"/>
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="FcI-Ph-m9e" secondAttribute="trailing" id="xgg-of-dPl"/>
<constraint firstItem="sDX-BN-qLw" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="yO9-oz-zRn"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Verify Master Password" id="NCb-RV-Vqq">
<leftBarButtonItems>
<barButtonItem title="Cancel" id="Xoh-Zv-hhd">
<connections>
<action selector="CancelButton_Activated:" destination="cn5-F4-59n" id="1gM-mE-phn"/>
</connections>
</barButtonItem>
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="nwd-aM-kFD" userLabel="Accoutn Switching Button">
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<action selector="AccountSwitchingBarButton_Activated:" destination="cn5-F4-59n" id="vVZ-IM-rkU"/>
</connections>
</barButtonItem>
</leftBarButtonItems>
<barButtonItem key="rightBarButtonItem" title="Submit" id="gju-yD-EmI">
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="dataSource" destination="7413" id="7415"/>
<outlet property="delegate" destination="7413" id="7416"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Verify Master Password" id="8265">
<barButtonItem key="leftBarButtonItem" title="Cancel" id="8268">
<connections>
<action selector="SubmitButton_Activated:" destination="cn5-F4-59n" id="O1U-fk-BDh"/>
<action selector="CancelButton_Activated:" destination="7413" id="8287"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" title="Submit" id="8269">
<connections>
<action selector="SubmitButton_Activated:" destination="7413" id="8288"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="AccountSwitchingBarButton" destination="nwd-aM-kFD" id="T8F-CN-2il"/>
<outlet property="CancelButton" destination="Xoh-Zv-hhd" id="mwi-4K-maj"/>
<outlet property="MainTableView" destination="FcI-Ph-m9e" id="Ybv-5r-VGA"/>
<outlet property="NavItem" destination="NCb-RV-Vqq" id="L9b-At-x0A"/>
<outlet property="OverlayView" destination="sDX-BN-qLw" id="veu-q4-CeW"/>
<outlet property="SubmitButton" destination="gju-yD-EmI" id="mjg-yP-09M"/>
<outlet property="CancelButton" destination="8268" id="name-outlet-8268"/>
<outlet property="MainTableView" destination="7414" id="name-outlet-7414"/>
<outlet property="NavItem" destination="8265" id="name-outlet-8265"/>
<outlet property="SubmitButton" destination="8269" id="name-outlet-8269"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vNI-gq-ubp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="7419" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="836" y="1407"/>
<point key="canvasLocation" x="955" y="1407"/>
</scene>
<!--Setup View Controller-->
<scene sceneID="10573">
<objects>
<viewController id="10570" customClass="SetupViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="10565"/>
<viewControllerLayoutGuide type="bottom" id="10566"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="10575">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Extension Activated!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11092">
<rect key="frame" x="15" y="30" width="384" height="20.5"/>
<rect key="frame" x="15" y="100" width="384" height="20.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="darkTextColor"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11093">
<rect key="frame" x="15" y="134.5" width="570" height="41"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="darkTextColor"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="check.png" translatesAutoresizingMaskIntoConstraints="NO" id="11094">
<rect key="frame" x="255" y="205.5" width="90" height="90"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="hcg-mr-Ilx"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="11092" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11114"/>
<constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
<constraint firstItem="11092" firstAttribute="top" secondItem="hcg-mr-Ilx" secondAttribute="top" constant="30" id="11116"/>
<constraint firstItem="11093" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11119"/>
<constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
<constraint firstItem="11092" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11114"/>
<constraint firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
<constraint firstItem="11092" firstAttribute="top" secondItem="10565" secondAttribute="bottom" constant="30" id="11116"/>
<constraint firstItem="11093" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11119"/>
<constraint firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
<constraint firstItem="11093" firstAttribute="top" secondItem="11092" secondAttribute="bottom" constant="20" id="11121"/>
<constraint firstItem="11094" firstAttribute="centerX" secondItem="hcg-mr-Ilx" secondAttribute="centerX" id="11122"/>
<constraint firstItem="11094" firstAttribute="centerX" secondItem="10575" secondAttribute="centerX" id="11122"/>
<constraint firstItem="11094" firstAttribute="top" secondItem="11093" secondAttribute="bottom" constant="30" id="11123"/>
</constraints>
</view>
@@ -507,7 +429,7 @@
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="10583">
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
@@ -528,7 +450,7 @@
<objects>
<tableViewController id="11543" customClass="LoginSearchViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="11545">
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<searchBar key="tableHeaderView" contentMode="redraw" id="13084">
@@ -541,10 +463,10 @@
</searchBar>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="11548">
<rect key="frame" x="0.0" y="88.5" width="414" height="44"/>
<rect key="frame" x="0.0" y="72" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="11548" id="11549">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
@@ -584,7 +506,7 @@
<objects>
<navigationController id="11552" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="11554">
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
@@ -600,19 +522,8 @@
<point key="canvasLocation" x="1920" y="908"/>
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="12574"/>
<segue reference="3731"/>
</inferredMetricsTieBreakers>
<resources>
<image name="check.png" width="90" height="90"/>
<image name="logo.png" width="282" height="44"/>
<image name="person.2" catalog="system" width="128" height="81"/>
<systemColor name="darkTextColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
</document>

View File

@@ -15,7 +15,7 @@ namespace Bit.iOS.Autofill.Utilities
{
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
UIViewController controller, IPasswordRepromptService passwordRepromptService,
UITableViewController controller, IPasswordRepromptService passwordRepromptService,
string loginAddSegue)
{
tableView.DeselectRow(indexPath, true);

View File

@@ -1,509 +0,0 @@
using System;
using UIKit;
using Foundation;
using Bit.iOS.Core.Views;
using Bit.App.Resources;
using Bit.iOS.Core.Utilities;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.Domain;
using Bit.Core.Enums;
using Bit.App.Pages;
using Bit.App.Models;
using Xamarin.Forms;
using Bit.Core;
namespace Bit.iOS.Core.Controllers
{
public abstract class BaseLockPasswordViewController : ExtendedUIViewController
{
private IVaultTimeoutService _vaultTimeoutService;
private ICryptoService _cryptoService;
private IDeviceActionService _deviceActionService;
private IStateService _stateService;
private IStorageService _secureStorageService;
private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
private bool _pinLock;
private bool _biometricLock;
private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false;
private bool _usesKeyConnector;
private bool _biometricUnlockOnly = false;
protected bool autofillExtension = false;
public BaseLockPasswordViewController(IntPtr handle)
: base(handle)
{ }
public abstract UINavigationItem BaseNavItem { get; }
public abstract UIBarButtonItem BaseCancelButton { get; }
public abstract UIBarButtonItem BaseSubmitButton { get; }
public abstract Action Success { get; }
public abstract Action Cancel { get; }
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
AppResources.MasterPassword, useButton: true);
public string BiometricIntegrityKey { get; set; }
public UITableViewCell BiometricCell
{
get
{
var cell = new UITableViewCell();
cell.BackgroundColor = ThemeHelpers.BackgroundColor;
if (_biometricIntegrityValid)
{
var biometricButtonText = _deviceActionService.SupportsFaceBiometric() ?
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;
cell.TextLabel.Text = biometricButtonText;
}
else
{
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
cell.TextLabel.Lines = 0;
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
cell.TextLabel.Text = AppResources.BiometricInvalidatedExtension;
}
return cell;
}
}
public abstract UITableView TableView { get; }
public override async void ViewDidLoad()
{
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
// We re-use the lock screen for autofill extension to verify master password
// when trying to access protected items.
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{
_passwordReprompt = true;
_isPinProtected = false;
_isPinProtectedWithKey = false;
_pinLock = false;
_biometricLock = false;
}
else
{
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _cryptoService.HasKeyAsync();
_biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
}
if (_pinLock)
{
BaseNavItem.Title = AppResources.VerifyPIN;
}
else if (_usesKeyConnector)
{
BaseNavItem.Title = AppResources.UnlockVault;
}
else
{
BaseNavItem.Title = AppResources.VerifyMasterPassword;
}
BaseCancelButton.Title = AppResources.Cancel;
if (_biometricUnlockOnly)
{
BaseSubmitButton.Title = null;
BaseSubmitButton.Enabled = false;
}
else
{
BaseSubmitButton.Title = AppResources.Submit;
}
var descriptor = UIFontDescriptor.PreferredBody;
if (!_biometricUnlockOnly)
{
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
{
CheckPasswordAsync().GetAwaiter().GetResult();
return true;
};
if (_pinLock)
{
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
}
MasterPasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
MasterPasswordCell.Button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
MasterPasswordCell.Button.TouchUpInside += (sender, e) =>
{
MasterPasswordCell.TextField.SecureTextEntry = !MasterPasswordCell.TextField.SecureTextEntry;
MasterPasswordCell.Button.SetTitle(MasterPasswordCell.TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
};
}
if (TableView != null)
{
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
}
TableView.RowHeight = UITableView.AutomaticDimension;
TableView.EstimatedRowHeight = 70;
TableView.Source = new TableSource(this);
TableView.AllowsSelection = true;
base.ViewDidLoad();
if (_biometricLock)
{
if (!_biometricIntegrityValid)
{
return;
}
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync());
});
}
}
public override async void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
// Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector)
{
if (!(_pinLock || _biometricLock) ||
(_biometricLock && !_biometricIntegrityValid))
{
PromptSSO();
}
}
else if (!_biometricLock || !_biometricIntegrityValid)
{
MasterPasswordCell.TextField.BecomeFirstResponder();
}
}
protected async Task CheckPasswordAsync()
{
if (string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok);
PresentViewController(alert, true, null);
return;
}
var email = await _stateService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock)
{
var failed = true;
try
{
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
else
{
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
failed = false;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2);
}
}
catch
{
failed = true;
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
return;
}
InvalidValue();
}
}
else
{
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null)
{
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey)
{
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash);
}
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
if (passwordValid)
{
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
}
else
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
return;
}
InvalidValue();
}
}
}
public async Task PromptBiometricAsync()
{
if (!_biometricLock || !_biometricIntegrityValid)
{
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
DoContinue();
}
}
public void PromptSSO()
{
var loginPage = new LoginSsoPage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesToPage(loginPage);
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
{
vm.SsoAuthSuccessAction = () => DoContinue();
vm.CloseAction = Cancel;
}
var navigationPage = new NavigationPage(loginPage);
var loginController = navigationPage.CreateViewController();
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginController, true, null);
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
{
var hasKey = await _cryptoService.HasKeyAsync();
if (!hasKey)
{
await _cryptoService.SetKeyAsync(key);
}
DoContinue(masterPassword);
}
private async void DoContinue(bool masterPassword = false)
{
if (masterPassword)
{
await _stateService.SetPasswordVerifiedAutofillAsync(true);
}
await EnableBiometricsIfNeeded();
await _stateService.SetBiometricLockedAsync(false);
MasterPasswordCell.TextField.ResignFirstResponder();
Success();
}
private async Task EnableBiometricsIfNeeded()
{
// Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync(BiometricIntegrityKey);
}
}
private void InvalidValue()
{
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) =>
{
MasterPasswordCell.TextField.Text = string.Empty;
MasterPasswordCell.TextField.BecomeFirstResponder();
});
PresentViewController(alert, true, null);
}
private async Task LogOutAsync()
{
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
var authService = ServiceContainer.Resolve<IAuthService>("authService");
authService.LogOut(() =>
{
Cancel?.Invoke();
});
}
public class TableSource : ExtendedUITableViewSource
{
private readonly BaseLockPasswordViewController _controller;
public TableSource(BaseLockPasswordViewController controller)
{
_controller = controller;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
if (indexPath.Section == 0)
{
if (indexPath.Row == 0)
{
if (_controller._biometricUnlockOnly)
{
return _controller.BiometricCell;
}
else
{
return _controller.MasterPasswordCell;
}
}
}
else if (indexPath.Section == 1)
{
if (indexPath.Row == 0)
{
if (_controller._passwordReprompt)
{
var cell = new ExtendedUITableViewCell();
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
cell.TextLabel.Lines = 0;
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
return cell;
}
else if (!_controller._biometricUnlockOnly)
{
return _controller.BiometricCell;
}
}
}
return new ExtendedUITableViewCell();
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return UITableView.AutomaticDimension;
}
public override nint NumberOfSections(UITableView tableView)
{
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
_controller._passwordReprompt
? 2
: 1;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
if (section <= 1)
{
return 1;
}
return 0;
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
return section == 1 ? 0.00001f : UITableView.AutomaticDimension;
}
public override string TitleForHeader(UITableView tableView, nint section)
{
return null;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
tableView.DeselectRow(indexPath, true);
tableView.EndEditing(true);
if (indexPath.Row == 0 &&
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
indexPath.Section == 1))
{
var task = _controller.PromptBiometricAsync();
return;
}
var cell = tableView.CellAt(indexPath);
if (cell == null)
{
return;
}
if (cell is ISelectable selectableCell)
{
selectableCell.Select();
}
}
}
}
}

View File

@@ -18,8 +18,6 @@ using Bit.Core;
namespace Bit.iOS.Core.Controllers
{
// TODO: Leaving this here until all inheritance is changed to use BaseLockPasswordViewController instead of UITableViewController
[Obsolete("Use BaseLockPasswordViewController instead")]
public abstract class LockPasswordViewController : ExtendedUITableViewController
{
private IVaultTimeoutService _vaultTimeoutService;

View File

@@ -1,16 +0,0 @@
using Bit.App.Controls;
using Bit.iOS.Core.Renderers.CollectionView;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExtendedCollectionView), typeof(ExtendedCollectionViewRenderer))]
namespace Bit.iOS.Core.Renderers.CollectionView
{
public class ExtendedCollectionViewRenderer : GroupableItemsViewRenderer<ExtendedCollectionView, GroupableItemsViewController<ExtendedCollectionView>>
{
protected override GroupableItemsViewController<ExtendedCollectionView> CreateController(ExtendedCollectionView itemsView, ItemsViewLayout layout)
{
return new ExtendedGroupableItemsViewController<ExtendedCollectionView>(itemsView, layout);
}
}
}

View File

@@ -1,28 +0,0 @@
using System;
using Bit.App.Controls;
using Foundation;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Core.Renderers.CollectionView
{
public class ExtendedGroupableItemsViewController<TItemsView> : GroupableItemsViewController<TItemsView>
where TItemsView : ExtendedCollectionView
{
public ExtendedGroupableItemsViewController(TItemsView groupableItemsView, ItemsViewLayout layout)
: base(groupableItemsView, layout)
{
}
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
{
try
{
base.UpdateTemplatedCell(cell, indexPath);
}
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
{
throw new Exception("Error in ExtendedCollectionView, extra data: " + ItemsView.ExtraDataForLogging, ex);
}
}
}
}

View File

@@ -16,10 +16,8 @@ namespace Bit.iOS.Core.Services
_stateService = stateService;
}
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
// isSensitive is only used by Android for now
int clearSeconds = -1;
if (expiresInMs < 0)
{
@@ -38,11 +36,5 @@ namespace Bit.iOS.Core.Services
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null
}));
}
public bool IsCopyNotificationHandledByPlatform()
{
// return true for any future versions of iOS that notify the user when text is copied to the clipboard
return false;
}
}
}

View File

@@ -1,101 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Core.Utilities
{
public class AccountSwitchingOverlayHelper
{
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
IStateService _stateService;
IMessagingService _messagingService;
ILogger _logger;
public AccountSwitchingOverlayHelper()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
}
public async Task<UIImage> CreateAvatarImageAsync()
{
try
{
if (_stateService is null)
{
throw new NullReferenceException(nameof(_stateService));
}
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
}
catch (Exception ex)
{
_logger.Exception(ex);
return UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
}
}
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
{
var overlay = new AccountSwitchingOverlayView()
{
LongPressAccountEnabled = false,
AfterHide = () =>
{
if (containerView != null)
{
containerView.Hidden = true;
}
}
};
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
FromIOSExtension = true
};
overlay.BindingContext = vm;
overlay.IsVisible = false;
var renderer = Platform.CreateRenderer(overlay.Content);
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
var view = renderer.NativeView;
view.TranslatesAutoresizingMaskIntoConstraints = false;
containerView.AddSubview(view);
containerView.AddConstraints(new NSLayoutConstraint[]
{
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, view, NSLayoutAttribute.Trailing, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, view, NSLayoutAttribute.Leading, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, view, NSLayoutAttribute.Top, 1f, 0f),
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, view, NSLayoutAttribute.Bottom, 1f, 0f)
});
containerView.Hidden = true;
return overlay;
}
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
{
var overlayVisible = accountSwitchingOverlayView.IsVisible;
if (!overlayVisible)
{
// So that the animation doesn't break we only care about showing it
// and the hiding if done through AccountSwitchingOverlayView -> AfterHide
containerView.Hidden = false;
}
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
containerView.UserInteractionEnabled = !overlayVisible;
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
}
}
}

View File

@@ -1,48 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Services;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Core.Utilities
{
public static class ImageSourceExtensions
{
/// <summary>
/// Gets the native image from the ImageSource.
/// Taken from https://github.com/xamarin/Xamarin.Forms/blob/02dee20dfa1365d0104758e534581d1fa5958990/Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs#L264
/// </summary>
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
{
if (source == null || source.IsEmpty)
{
return null;
}
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
if (handler == null)
{
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed cause IImageSourceHandler couldn't be found"));
return null;
}
try
{
float scale = (float)UIScreen.MainScreen.Scale;
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
}
catch (OperationCanceledException)
{
LoggerHelper.LogEvenIfCantBeResolved(new OperationCanceledException("GetNativeImageAsync was cancelled"));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed", ex));
}
return null;
}
}
}

View File

@@ -1,20 +0,0 @@
using UIKit;
namespace Bit.iOS.Core.Utilities
{
public static class UISearchBarExtensions
{
public static void UpdateThemeIfNeeded(this UISearchBar searchBar)
{
if (!ThemeHelpers.LightTheme)
{
searchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
searchBar.SearchTextField.TextColor = UIColor.White;
searchBar.SearchTextField.LeftView.TintColor = UIColor.White;
}
}
}
}
}

View File

@@ -7,7 +7,6 @@ using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -59,13 +58,13 @@ namespace Bit.iOS.Core.Utilities
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var deviceActionService = new DeviceActionService(stateService, messagingService);
var clipboardService = new ClipboardService(stateService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService(mobileStorageService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
@@ -173,15 +172,6 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
if (postBootstrapFunc != null)
{
await postBootstrapFunc.Invoke();

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@@ -12,6 +6,12 @@ using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Models;
using Bit.iOS.Core.Utilities;
using Foundation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UIKit;
namespace Bit.iOS.Core.Views
@@ -122,10 +122,7 @@ namespace Bit.iOS.Core.Views
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
if (Items == null
|| !Items.Any()
|| cell?.TextLabel == null
|| cell.DetailTextLabel == null)
if (Items == null || Items.Count() == 0 || cell == null)
{
return;
}

View File

@@ -138,7 +138,6 @@
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Effects\" />
<Folder Include="Renderers\CollectionView\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Constants.cs" />
@@ -195,12 +194,6 @@
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\FontElementExtensions.cs" />
<Compile Include="Effects\ScrollViewContentInsetAdjustmentBehaviorEffect.cs" />
<Compile Include="Utilities\AccountSwitchingOverlayHelper.cs" />
<Compile Include="Utilities\ImageSourceExtensions.cs" />
<Compile Include="Controllers\BaseLockPasswordViewController.cs" />
<Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" />
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" />
<Compile Include="Utilities\UISearchBarExtensions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\App\App.csproj">

View File

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

View File

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

View File

@@ -167,7 +167,7 @@
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="Microsoft.AppCenter.Crashes">
<Version>4.4.0</Version>
</PackageReference>

View File

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

View File

@@ -183,7 +183,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version>
<Version>1.7.2</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />