mirror of
https://github.com/bitwarden/mobile
synced 2026-01-06 18:43:43 +00:00
Compare commits
43 Commits
v2022.05.0
...
v2022.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe128b932 | ||
|
|
df2e52f82c | ||
|
|
2d224f5e22 | ||
|
|
4831097c0b | ||
|
|
72f2983885 | ||
|
|
ee68dcf3b0 | ||
|
|
daab473cbb | ||
|
|
e6b99270cf | ||
|
|
a8d9aaa7fe | ||
|
|
542ef5f31a | ||
|
|
0b2fc2a647 | ||
|
|
a95fdf67a1 | ||
|
|
73890162bf | ||
|
|
ef14a8f850 | ||
|
|
51a5f58258 | ||
|
|
388ad4e840 | ||
|
|
48a8d9ae35 | ||
|
|
dd6003bd4f | ||
|
|
fba407f3b6 | ||
|
|
88b406544b | ||
|
|
3438ed94ce | ||
|
|
ec71b21264 | ||
|
|
b223f5f16e | ||
|
|
0a64e4c918 | ||
|
|
9b41db962e | ||
|
|
43d3c7b5d7 | ||
|
|
8168089591 | ||
|
|
6b55fc3032 | ||
|
|
87ab42b155 | ||
|
|
98130e89de | ||
|
|
121f0e3628 | ||
|
|
8a3d88b3ce | ||
|
|
b8b41fe847 | ||
|
|
5bbef3ee16 | ||
|
|
9a2b6c8ec9 | ||
|
|
5272c99643 | ||
|
|
43e9515a03 | ||
|
|
7e9b7398c8 | ||
|
|
58d7b001a5 | ||
|
|
a259560d29 | ||
|
|
22c746543a | ||
|
|
bcbc2738ca | ||
|
|
604e3b6892 |
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -21,12 +21,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing requirements
|
|
||||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Before you submit
|
## 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)
|
- [ ] 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 requires a **documentation update** (notify the documentation team)
|
||||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
|
|||||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -60,6 +60,11 @@ jobs:
|
|||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -209,6 +214,11 @@ jobs:
|
|||||||
name: F-Droid Build
|
name: F-Droid Build
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -304,18 +314,6 @@ jobs:
|
|||||||
|
|
||||||
$xml.Save($androidPath);
|
$xml.Save($androidPath);
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Uninstall from App.csproj"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($appPath);
|
|
||||||
|
|
||||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
|
||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
|
||||||
|
|
||||||
$xml.Save($appPath);
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
Write-Output "##### Uninstall from Core.csproj"
|
Write-Output "##### Uninstall from Core.csproj"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
@@ -380,6 +378,11 @@ jobs:
|
|||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
nuget help | grep Version
|
nuget help | grep Version
|
||||||
|
|||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -34,29 +34,13 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||||
|
|
||||||
- name: Retrieve Mobile release version
|
- name: Check Release Version
|
||||||
id: retrieve-mobile-version
|
id: version
|
||||||
run: |
|
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
|
||||||
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
|
with:
|
||||||
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
|
release-type: ${{ github.event.inputs.release_type }}
|
||||||
echo "::set-output name=mobile_version::${ver}"
|
project-type: xamarin
|
||||||
shell: bash
|
file: src/Android/Properties/AndroidManifest.xml
|
||||||
|
|
||||||
- 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
|
- name: Get branch name
|
||||||
id: branch
|
id: branch
|
||||||
@@ -83,8 +67,8 @@ jobs:
|
|||||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
||||||
./Bitwarden iOS.zip"
|
./Bitwarden iOS.zip"
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
tag: v${{ steps.version.outputs.version }}
|
||||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
name: Version ${{ steps.version.outputs.version }}
|
||||||
body: "<insert release notes here>"
|
body: "<insert release notes here>"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.2</Version>
|
<Version>1.7.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>122.0.0</Version>
|
<Version>122.0.0</Version>
|
||||||
@@ -145,12 +145,12 @@
|
|||||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
|
||||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||||
<Compile Include="WebAuthCallbackActivity.cs" />
|
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||||
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
||||||
<Compile Include="Services\ClipboardService.cs" />
|
<Compile Include="Services\ClipboardService.cs" />
|
||||||
<Compile Include="Utilities\IntentExtensions.cs" />
|
<Compile Include="Utilities\IntentExtensions.cs" />
|
||||||
|
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ namespace Bit.Droid
|
|||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG && !FDROID
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
|
||||||
var appCenterTask = appCenterHelper.InitAsync();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var toplayout = Window?.DecorView?.RootView;
|
var toplayout = Window?.DecorView?.RootView;
|
||||||
if (toplayout != null)
|
if (toplayout != null)
|
||||||
@@ -85,6 +82,7 @@ namespace Bit.Droid
|
|||||||
_appOptions = GetOptions();
|
_appOptions = GetOptions();
|
||||||
LoadApplication(new App.App(_appOptions));
|
LoadApplication(new App.App(_appOptions));
|
||||||
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "startEventTimer")
|
if (message.Command == "startEventTimer")
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Services;
|
using Bit.Droid.Services;
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Plugin.Fingerprint;
|
using Plugin.Fingerprint;
|
||||||
using Xamarin.Android.Net;
|
using Xamarin.Android.Net;
|
||||||
@@ -20,6 +19,7 @@ using System.Net.Http;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
@@ -62,6 +62,15 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
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 !FDROID
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
@@ -121,13 +130,14 @@ namespace Bit.Droid
|
|||||||
var secureStorageService = new SecureStorageService();
|
var secureStorageService = new SecureStorageService();
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService,
|
var clipboardService = new ClipboardService(stateService);
|
||||||
|
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
|
||||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||||
broadcasterService);
|
messagingService, broadcasterService);
|
||||||
var biometricService = new BiometricService();
|
var biometricService = new BiometricService();
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||||
@@ -142,7 +152,7 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||||
ServiceContainer.Register<IStateService>("stateService", stateService);
|
ServiceContainer.Register<IStateService>("stateService", stateService);
|
||||||
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
||||||
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
|
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using AndroidX.AppCompat.Widget;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomPageRenderer : PageRenderer
|
||||||
|
{
|
||||||
|
public CustomPageRenderer(Context context) : base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
Activity context = (Activity)this.Context;
|
||||||
|
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
|
||||||
|
if(toolbar != null)
|
||||||
|
{
|
||||||
|
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -3,11 +3,10 @@ using System.Threading.Tasks;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Security.Keystore;
|
using Android.Security.Keystore;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Java.Security;
|
using Java.Security;
|
||||||
using Javax.Crypto;
|
using Javax.Crypto;
|
||||||
#if !FDROID
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
@@ -74,9 +73,7 @@ namespace Bit.Droid.Services
|
|||||||
catch (InvalidKeyException e)
|
catch (InvalidKeyException e)
|
||||||
{
|
{
|
||||||
// Fallback for old bitwarden users without a key
|
// Fallback for old bitwarden users without a key
|
||||||
#if !FDROID
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||||
Crashes.TrackError(e);
|
|
||||||
#endif
|
|
||||||
CreateKey();
|
CreateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +98,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
||||||
// is not functioning
|
// is not functioning
|
||||||
#if !FDROID
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||||
Crashes.TrackError(e);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Bit.Core;
|
using Android.OS;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
@@ -26,13 +26,41 @@ namespace Bit.Droid.Services
|
|||||||
PendingIntentFlags.UpdateCurrent));
|
PendingIntentFlags.UpdateCurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
await Clipboard.SetTextAsync(text);
|
// 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 ClearClipboardAlarmAsync(expiresInMs);
|
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)
|
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
|
||||||
{
|
{
|
||||||
var clearMs = expiresInMs;
|
var clearMs = expiresInMs;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
public class DeviceActionService : IDeviceActionService
|
public class DeviceActionService : IDeviceActionService
|
||||||
{
|
{
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
@@ -47,11 +48,13 @@ namespace Bit.Droid.Services
|
|||||||
private string _userAgent;
|
private string _userAgent;
|
||||||
|
|
||||||
public DeviceActionService(
|
public DeviceActionService(
|
||||||
|
IClipboardService clipboardService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IMessagingService messagingService,
|
IMessagingService messagingService,
|
||||||
IBroadcasterService broadcasterService,
|
IBroadcasterService broadcasterService,
|
||||||
Func<IEventService> eventServiceFunc)
|
Func<IEventService> eventServiceFunc)
|
||||||
{
|
{
|
||||||
|
_clipboardService = clipboardService;
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
@@ -929,20 +932,12 @@ namespace Bit.Droid.Services
|
|||||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||||
if (totp != null)
|
if (totp != null)
|
||||||
{
|
{
|
||||||
CopyToClipboard(totp);
|
await _clipboardService.CopyTextAsync(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()
|
public float GetSystemFontSizeScale()
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#if !FDROID
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AppCenter;
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Utilities
|
|
||||||
{
|
|
||||||
public class AppCenterHelper
|
|
||||||
{
|
|
||||||
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
|
||||||
|
|
||||||
private readonly IAppIdService _appIdService;
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
|
|
||||||
private string _userId;
|
|
||||||
private string _appId;
|
|
||||||
|
|
||||||
public AppCenterHelper(
|
|
||||||
IAppIdService appIdService,
|
|
||||||
IStateService stateService)
|
|
||||||
{
|
|
||||||
_appIdService = appIdService;
|
|
||||||
_stateService = stateService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
|
||||||
{
|
|
||||||
_userId = await _stateService.GetActiveUserIdAsync();
|
|
||||||
_appId = await _appIdService.GetAppIdAsync();
|
|
||||||
|
|
||||||
AppCenter.Start(AppSecret, typeof(Crashes));
|
|
||||||
AppCenter.SetUserId(_userId);
|
|
||||||
|
|
||||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
|
||||||
{
|
|
||||||
return new ErrorAttachmentLog[]
|
|
||||||
{
|
|
||||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(new
|
|
||||||
{
|
|
||||||
AppId = _appId,
|
|
||||||
UserId = _userId
|
|
||||||
}, Formatting.Indented);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
12
src/App/Abstractions/IAccountsManager.cs
Normal file
12
src/App/Abstractions/IAccountsManager.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal file
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
<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="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -129,6 +128,7 @@
|
|||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -421,5 +421,6 @@
|
|||||||
<None Remove="Behaviors\" />
|
<None Remove="Behaviors\" />
|
||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Bit.App.Pages;
|
|||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
@@ -16,7 +17,7 @@ using Xamarin.Forms.Xaml;
|
|||||||
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
||||||
namespace Bit.App
|
namespace Bit.App
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application, IAccountsManagerHost
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
@@ -27,6 +28,7 @@ namespace Bit.App
|
|||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IAccountsManager _accountsManager;
|
||||||
|
|
||||||
private static bool _isResumed;
|
private static bool _isResumed;
|
||||||
|
|
||||||
@@ -47,6 +49,9 @@ namespace Bit.App
|
|||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
|
||||||
|
_accountsManager.Init(() => Options, this);
|
||||||
|
|
||||||
Bootstrap();
|
Bootstrap();
|
||||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||||
@@ -71,30 +76,6 @@ namespace Bit.App
|
|||||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
_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")
|
else if (message.Command == "resumed")
|
||||||
{
|
{
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
@@ -109,22 +90,10 @@ namespace Bit.App
|
|||||||
await SleptAsync();
|
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")
|
else if (message.Command == "migrated")
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
await SetMainPageAsync();
|
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
}
|
}
|
||||||
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||||
message.Command == "popAllAndGoToTabMyVault" ||
|
message.Command == "popAllAndGoToTabMyVault" ||
|
||||||
@@ -168,7 +137,6 @@ namespace Bit.App
|
|||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +202,7 @@ namespace Bit.App
|
|||||||
|
|
||||||
private async Task ResumedAsync()
|
private async Task ResumedAsync()
|
||||||
{
|
{
|
||||||
|
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
_messagingService.Send("startEventTimer");
|
_messagingService.Send("startEventTimer");
|
||||||
await UpdateThemeAsync();
|
await UpdateThemeAsync();
|
||||||
@@ -263,102 +232,6 @@ namespace Bit.App
|
|||||||
new System.Globalization.UmAlQuraCalendar();
|
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()
|
private async Task ClearCacheIfNeededAsync()
|
||||||
{
|
{
|
||||||
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
||||||
@@ -420,7 +293,7 @@ namespace Bit.App
|
|||||||
UpdateThemeAsync();
|
UpdateThemeAsync();
|
||||||
};
|
};
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
var mainPageTask = SetMainPageAsync();
|
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,23 +314,8 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool userInitiated)
|
public async Task SetPreviousPageInfoAsync()
|
||||||
{
|
{
|
||||||
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;
|
PreviousPageInfo lastPageBeforeLock = null;
|
||||||
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -483,8 +341,44 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
|
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
|
||||||
var lockPage = new LockPage(Options, autoPromptBiometric);
|
}
|
||||||
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
||||||
|
|
||||||
|
public bool LongPressAccountEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
public Action AfterHide { get; set; }
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -135,6 +139,8 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
// remove overlay
|
// remove overlay
|
||||||
IsVisible = false;
|
IsVisible = false;
|
||||||
|
|
||||||
|
AfterHide?.Invoke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +173,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (!item.IsAccount)
|
if (!LongPressAccountEnabled || !item.IsAccount)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,23 +45,28 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public ICommand LongPressAccountCommand { get; }
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
|
public bool FromIOSExtension { get; set; }
|
||||||
|
|
||||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (item.AccountView.IsAccount)
|
if (!item.AccountView.IsAccount)
|
||||||
{
|
{
|
||||||
if (!item.AccountView.IsActive)
|
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.AccountView.IsActive)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
if (FromIOSExtension)
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
|
||||||
else if (AllowActiveAccountSelection)
|
|
||||||
{
|
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (AllowActiveAccountSelection)
|
||||||
{
|
{
|
||||||
_messagingService.Send("addAccount");
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public class ExtendedCollectionView : CollectionView
|
public class ExtendedCollectionView : CollectionView
|
||||||
{
|
{
|
||||||
|
public string ExtraDataForLogging { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPolicyInEffect
|
public bool IsPolicyInEffect
|
||||||
@@ -68,6 +72,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
|
|||||||
@@ -80,7 +80,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
@@ -119,7 +120,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string Pin { get; set; }
|
public string Pin { get; set; }
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
|
|||||||
@@ -101,7 +101,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ namespace Bit.App.Pages
|
|||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|||||||
@@ -81,10 +81,12 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
|
string ssoToken;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _apiService.PreValidateSso(OrgIdentifier);
|
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
|
ssoToken = response.Token;
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -112,7 +114,8 @@ namespace Bit.App.Pages
|
|||||||
"response_type=code&scope=api%20offline_access&" +
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
"code_challenge_method=S256&response_mode=query&" +
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
|
||||||
|
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -68,7 +68,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -106,7 +107,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
|
|||||||
@@ -107,7 +107,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -145,7 +146,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPolicyInEffect
|
public bool IsPolicyInEffect
|
||||||
@@ -86,6 +90,7 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
@@ -214,7 +219,8 @@ namespace Bit.App.Pages
|
|||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
ResetPasswordKey = encryptedKey.EncryptedString
|
ResetPasswordKey = encryptedKey.EncryptedString,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
};
|
};
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
// Enroll user
|
// Enroll user
|
||||||
|
|||||||
@@ -105,7 +105,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
@@ -140,7 +141,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Generator History Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
|
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ namespace Bit.App.Pages
|
|||||||
private async void CopyAsync(GeneratedPasswordHistory ph)
|
private async void CopyAsync(GeneratedPasswordHistory ph)
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(ph.Password);
|
await _clipboardService.CopyTextAsync(ph.Password);
|
||||||
_platformUtilsService.ShowToast("info", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateOnThemeChanged()
|
public async Task UpdateOnThemeChanged()
|
||||||
|
|||||||
@@ -183,52 +183,68 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="A-Z"
|
Text="A-Z"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Uppercase}"
|
IsToggled="{Binding Uppercase}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="a-z"
|
Text="a-z"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Lowercase}"
|
IsToggled="{Binding Lowercase}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="0-9"
|
Text="0-9"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Number}"
|
IsToggled="{Binding Number}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="!@#$%^&*"
|
Text="!@#$%^&*"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Special}"
|
IsToggled="{Binding Special}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -277,7 +293,7 @@
|
|||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AvoidAmbiguous}"
|
IsToggled="{Binding AvoidAmbiguousChars}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -23,7 +22,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _lowercase;
|
private bool _lowercase;
|
||||||
private bool _number;
|
private bool _number;
|
||||||
private bool _special;
|
private bool _special;
|
||||||
private bool _avoidAmbiguous;
|
private bool _allowAmbiguousChars;
|
||||||
private int _minNumber;
|
private int _minNumber;
|
||||||
private int _minSpecial;
|
private int _minSpecial;
|
||||||
private int _length = 5;
|
private int _length = 5;
|
||||||
@@ -130,19 +129,29 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AvoidAmbiguous
|
public bool AllowAmbiguousChars
|
||||||
{
|
{
|
||||||
get => _avoidAmbiguous;
|
get => _allowAmbiguousChars;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _avoidAmbiguous, value))
|
if (SetProperty(ref _allowAmbiguousChars, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(AvoidAmbiguousChars)
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
_options.Ambiguous = !value;
|
_options.AllowAmbiguousChar = value;
|
||||||
var task = SaveOptionsAsync();
|
var task = SaveOptionsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AvoidAmbiguousChars
|
||||||
|
{
|
||||||
|
get => !AllowAmbiguousChars;
|
||||||
|
set => AllowAmbiguousChars = !value;
|
||||||
|
}
|
||||||
|
|
||||||
public int MinNumber
|
public int MinNumber
|
||||||
{
|
{
|
||||||
get => _minNumber;
|
get => _minNumber;
|
||||||
@@ -309,13 +318,12 @@ namespace Bit.App.Pages
|
|||||||
public async Task CopyAsync()
|
public async Task CopyAsync()
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(Password);
|
await _clipboardService.CopyTextAsync(Password);
|
||||||
_platformUtilsService.ShowToast("success", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFromOptions()
|
private void LoadFromOptions()
|
||||||
{
|
{
|
||||||
AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
|
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
|
||||||
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
||||||
IsPassword = TypeSelectedIndex == 0;
|
IsPassword = TypeSelectedIndex == 0;
|
||||||
MinNumber = _options.MinNumber.GetValueOrDefault();
|
MinNumber = _options.MinNumber.GetValueOrDefault();
|
||||||
@@ -333,7 +341,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void SetOptions()
|
private void SetOptions()
|
||||||
{
|
{
|
||||||
_options.Ambiguous = !AvoidAmbiguous;
|
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
||||||
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
||||||
_options.MinNumber = MinNumber;
|
_options.MinNumber = MinNumber;
|
||||||
_options.MinSpecial = MinSpecial;
|
_options.MinSpecial = MinSpecial;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||||
x:Class="Bit.App.Pages.SendAddEditPage"
|
x:Class="Bit.App.Pages.SendAddEditPage"
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
Clicked="FileType_Clicked"
|
Clicked="FileType_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n File}"
|
AutomationProperties.Name="{u:I18n File}"
|
||||||
|
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -132,6 +134,7 @@
|
|||||||
Clicked="TextType_Clicked"
|
Clicked="TextType_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Text}"
|
AutomationProperties.Name="{u:I18n Text}"
|
||||||
|
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -250,28 +253,31 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="0">
|
Spacing="0"
|
||||||
|
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n Options}"
|
Text="{u:I18n Options}"
|
||||||
x:Name="_btnOptions"
|
x:Name="_btnOptions"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
Clicked="ToggleOptions_Clicked"/>
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsUp"
|
x:Name="_btnOptionsUp"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
IsVisible="{Binding ShowOptions}"
|
||||||
IsVisible="{Binding ShowOptions}" />
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsDown"
|
x:Name="_btnOptionsDown"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
||||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding ShowOptions}">
|
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -438,7 +444,8 @@
|
|||||||
Command="{Binding TogglePasswordCommand}"
|
Command="{Binding TogglePasswordCommand}"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
|||||||
@@ -209,11 +209,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleOptions_Clicked(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_vm.ToggleOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
|
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
nameof(IsText),
|
nameof(IsText),
|
||||||
nameof(IsFile),
|
nameof(IsFile),
|
||||||
|
nameof(FileTypeAccessibilityLabel),
|
||||||
|
nameof(TextTypeAccessibilityLabel)
|
||||||
};
|
};
|
||||||
private bool _disableHideEmail;
|
private bool _disableHideEmail;
|
||||||
private bool _sendOptionsPolicyInEffect;
|
private bool _sendOptionsPolicyInEffect;
|
||||||
@@ -59,6 +61,7 @@ namespace Bit.App.Pages
|
|||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
|
ToggleOptionsCommand = new Command(ToggleOptions);
|
||||||
|
|
||||||
TypeOptions = new List<KeyValuePair<string, SendType>>
|
TypeOptions = new List<KeyValuePair<string, SendType>>
|
||||||
{
|
{
|
||||||
@@ -89,6 +92,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
|
public Command ToggleOptionsCommand { get; set; }
|
||||||
public string SendId { get; set; }
|
public string SendId { get; set; }
|
||||||
public int SegmentedButtonHeight { get; set; }
|
public int SegmentedButtonHeight { get; set; }
|
||||||
public int SegmentedButtonFontSize { get; set; }
|
public int SegmentedButtonFontSize { get; set; }
|
||||||
@@ -102,6 +106,7 @@ namespace Bit.App.Pages
|
|||||||
public bool DisableHideEmailControl { get; set; }
|
public bool DisableHideEmailControl { get; set; }
|
||||||
public bool IsAddFromShare { get; set; }
|
public bool IsAddFromShare { get; set; }
|
||||||
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
||||||
|
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
|
||||||
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
||||||
@@ -134,7 +139,11 @@ namespace Bit.App.Pages
|
|||||||
public bool ShowOptions
|
public bool ShowOptions
|
||||||
{
|
{
|
||||||
get => _showOptions;
|
get => _showOptions;
|
||||||
set => SetProperty(ref _showOptions, value);
|
set => SetProperty(ref _showOptions, value,
|
||||||
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(OptionsAccessilibityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public int ExpirationDateTypeSelectedIndex
|
public int ExpirationDateTypeSelectedIndex
|
||||||
{
|
{
|
||||||
@@ -211,7 +220,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[]
|
additionalPropertyNames: new[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool DisableHideEmail
|
public bool DisableHideEmail
|
||||||
@@ -231,6 +241,9 @@ namespace Bit.App.Pages
|
|||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Bit.App.Pages.SendGroupingsPage"
|
x:Class="Bit.App.Pages.SendGroupingsPage"
|
||||||
@@ -138,7 +138,8 @@
|
|||||||
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Send Groupings Page" />
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@@ -66,7 +66,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Sends Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:SendView">
|
<DataTemplate x:DataType="views:SendView">
|
||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ConfirmYourIdentity}"
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
|
|||||||
@@ -109,7 +109,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseOTPVerification
|
public bool UseOTPVerification
|
||||||
@@ -139,6 +143,7 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
|
|
||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Folders Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:FolderView">
|
<DataTemplate x:DataType="views:FolderView">
|
||||||
<controls:ExtendedStackLayout
|
<controls:ExtendedStackLayout
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Settings Page" />
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _vm.UpdatePinAsync();
|
await _vm.UpdatePinAsync();
|
||||||
}
|
}
|
||||||
|
else if (item.Name == AppResources.SubmitCrashLogs)
|
||||||
|
{
|
||||||
|
await _vm.LoggerReportingAsync();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var biometricName = AppResources.Biometrics;
|
var biometricName = AppResources.Biometrics;
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using ZXing.Client.Result;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
|
private readonly ILogger _loggerService;
|
||||||
private const int CustomVaultTimeoutValue = -100;
|
private const int CustomVaultTimeoutValue = -100;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
@@ -39,6 +39,7 @@ namespace Bit.App.Pages
|
|||||||
private string _vaultTimeoutDisplayValue;
|
private string _vaultTimeoutDisplayValue;
|
||||||
private string _vaultTimeoutActionDisplayValue;
|
private string _vaultTimeoutActionDisplayValue;
|
||||||
private bool _showChangeMasterPassword;
|
private bool _showChangeMasterPassword;
|
||||||
|
private bool _reportLoggingEnabled;
|
||||||
|
|
||||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
@@ -79,6 +80,7 @@ namespace Bit.App.Pages
|
|||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
@@ -123,7 +125,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||||
!await _keyConnectorService.GetUsesKeyConnector();
|
!await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +288,26 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task LoggerReportingAsync()
|
||||||
|
{
|
||||||
|
var options = new[]
|
||||||
|
{
|
||||||
|
CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled),
|
||||||
|
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
||||||
|
};
|
||||||
|
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
|
||||||
|
|
||||||
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes));
|
||||||
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
|
BuildList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task VaultTimeoutActionAsync()
|
public async Task VaultTimeoutActionAsync()
|
||||||
{
|
{
|
||||||
var options = _vaultTimeoutActions.Select(o =>
|
var options = _vaultTimeoutActions.Select(o =>
|
||||||
@@ -494,11 +516,19 @@ namespace Bit.App.Pages
|
|||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherItems = new List<SettingsPageListItem>
|
var otherItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.Options },
|
new SettingsPageListItem { Name = AppResources.Options },
|
||||||
new SettingsPageListItem { Name = AppResources.About },
|
new SettingsPageListItem { Name = AppResources.About },
|
||||||
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
||||||
|
#if !FDROID
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.SubmitCrashLogs,
|
||||||
|
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||||
};
|
};
|
||||||
@@ -576,5 +606,9 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||||
|
|
||||||
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Bit.App.Effects;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Effects;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -10,8 +12,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class TabsPage : TabbedPage
|
public class TabsPage : TabbedPage
|
||||||
{
|
{
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
private NavigationPage _groupingsPage;
|
private NavigationPage _groupingsPage;
|
||||||
private NavigationPage _sendGroupingsPage;
|
private NavigationPage _sendGroupingsPage;
|
||||||
@@ -19,6 +23,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||||
{
|
{
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
@@ -78,12 +83,26 @@ namespace Bit.App.Pages
|
|||||||
protected override async void OnAppearing()
|
protected override async void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||||
|
{
|
||||||
|
if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await UpdateVaultButtonTitleAsync();
|
||||||
if (await _keyConnectorService.UserNeedsMigration())
|
if (await _keyConnectorService.UserNeedsMigration())
|
||||||
{
|
{
|
||||||
_messagingService.Send("convertAccountToKeyConnector");
|
_messagingService.Send("convertAccountToKeyConnector");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_broadcasterService.Unsubscribe(nameof(TabsPage));
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetToVaultPage()
|
public void ResetToVaultPage()
|
||||||
{
|
{
|
||||||
CurrentPage = _groupingsPage;
|
CurrentPage = _groupingsPage;
|
||||||
@@ -131,5 +150,19 @@ namespace Bit.App.Pages
|
|||||||
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateVaultButtonTitleAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||||
|
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
CipherType? type = null,
|
CipherType? type = null,
|
||||||
string folderId = null,
|
string folderId = null,
|
||||||
string collectionId = null,
|
string collectionId = null,
|
||||||
|
string organizationId = null,
|
||||||
string name = null,
|
string name = null,
|
||||||
string uri = null,
|
string uri = null,
|
||||||
bool fromAutofill = false,
|
bool fromAutofill = false,
|
||||||
@@ -51,6 +52,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.CipherId = cipherId;
|
_vm.CipherId = cipherId;
|
||||||
_vm.FolderId = folderId == "none" ? null : folderId;
|
_vm.FolderId = folderId == "none" ? null : folderId;
|
||||||
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
|
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
|
||||||
|
_vm.OrganizationId = organizationId;
|
||||||
_vm.Type = type;
|
_vm.Type = type;
|
||||||
_vm.DefaultName = name ?? appOptions?.SaveName;
|
_vm.DefaultName = name ?? appOptions?.SaveName;
|
||||||
_vm.DefaultUri = uri ?? appOptions?.Uri;
|
_vm.DefaultUri = uri ?? appOptions?.Uri;
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool ShowCardNumber
|
public bool ShowCardNumber
|
||||||
@@ -298,6 +299,7 @@ namespace Bit.App.Pages
|
|||||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||||
public bool AllowPersonal { get; set; }
|
public bool AllowPersonal { get; set; }
|
||||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,8 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Autofill Ciphers Page" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
Text=""
|
Text=""
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Clicked="BackButton_Clicked"
|
Clicked="BackButton_Clicked"
|
||||||
x:Name="_backButton" />
|
x:Name="_backButton"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n TapToGoBack}"/>
|
||||||
<controls:ExtendedSearchBar
|
<controls:ExtendedSearchBar
|
||||||
x:Name="_searchBar"
|
x:Name="_searchBar"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
@@ -47,6 +49,31 @@
|
|||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
|
<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}"
|
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
|
||||||
StyleClass="text-muted"
|
StyleClass="text-muted"
|
||||||
@@ -66,7 +93,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Ciphers Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:CipherView">
|
<DataTemplate x:DataType="views:CipherView">
|
||||||
<controls:CipherViewCell
|
<controls:CipherViewCell
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace Bit.App.Pages
|
|||||||
private CiphersPageViewModel _vm;
|
private CiphersPageViewModel _vm;
|
||||||
private bool _hasFocused;
|
private bool _hasFocused;
|
||||||
|
|
||||||
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string autofillUrl = null, bool deleted = false)
|
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
|
||||||
|
string autofillUrl = null, bool deleted = false)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as CiphersPageViewModel;
|
_vm = BindingContext as CiphersPageViewModel;
|
||||||
@@ -33,6 +34,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.PageTitle = AppResources.SearchVault;
|
_vm.PageTitle = AppResources.SearchVault;
|
||||||
}
|
}
|
||||||
|
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class CiphersPageViewModel : BaseViewModel
|
public class CiphersPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
@@ -23,7 +23,10 @@ namespace Bit.App.Pages
|
|||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
private CancellationTokenSource _searchCancellationTokenSource;
|
private CancellationTokenSource _searchCancellationTokenSource;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private bool _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
@@ -37,6 +40,9 @@ namespace Bit.App.Pages
|
|||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
@@ -48,6 +54,11 @@ namespace Bit.App.Pages
|
|||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
public bool Deleted { 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
|
public bool ShowNoData
|
||||||
{
|
{
|
||||||
get => _showNoData;
|
get => _showNoData;
|
||||||
@@ -76,11 +87,9 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
await InitVaultFilterAsync(true);
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
PerformSearchIfPopulated();
|
||||||
{
|
|
||||||
Search((Page as CiphersPage).SearchBar.Text, 200);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Search(string searchText, int? timeout = null)
|
public void Search(string searchText, int? timeout = null)
|
||||||
@@ -107,8 +116,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
||||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||||
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
|
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
||||||
cts.Token.ThrowIfCancellationRequested();
|
cts.Token.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -192,6 +202,19 @@ 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)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
x:DataType="pages:GroupingsPageViewModel"
|
x:DataType="pages:GroupingsPageViewModel"
|
||||||
Title="{Binding PageTitle}"
|
Title="{Binding PageTitle}"
|
||||||
x:Name="_page">
|
x:Name="_page">
|
||||||
@@ -106,6 +107,30 @@
|
|||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
|
<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>
|
||||||
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Padding="20, 0"
|
Padding="20, 0"
|
||||||
@@ -130,7 +155,8 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Groupings Page" />
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ namespace Bit.App.Pages
|
|||||||
private PreviousPageInfo _previousPage;
|
private PreviousPageInfo _previousPage;
|
||||||
|
|
||||||
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
||||||
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
|
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
|
||||||
bool deleted = false)
|
PreviousPageInfo previousPage = null, bool deleted = false)
|
||||||
{
|
{
|
||||||
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -52,6 +52,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.PageTitle = pageTitle;
|
_vm.PageTitle = pageTitle;
|
||||||
}
|
}
|
||||||
|
if (vaultFilterSelection != null)
|
||||||
|
{
|
||||||
|
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||||
|
}
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -259,7 +263,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (!_vm.Deleted && DoOnce())
|
if (!_vm.Deleted && DoOnce())
|
||||||
{
|
{
|
||||||
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
|
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageViewModel : BaseViewModel
|
public class GroupingsPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private const int NoFolderListSize = 100;
|
private const int NoFolderListSize = 100;
|
||||||
|
|
||||||
@@ -46,6 +46,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public GroupingsPageViewModel()
|
public GroupingsPageViewModel()
|
||||||
@@ -60,10 +62,11 @@ namespace Bit.App.Pages
|
|||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.MyVault;
|
|
||||||
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
@@ -87,8 +90,9 @@ namespace Bit.App.Pages
|
|||||||
public bool HasCiphers { get; set; }
|
public bool HasCiphers { get; set; }
|
||||||
public bool HasFolders { get; set; }
|
public bool HasFolders { get; set; }
|
||||||
public bool HasCollections { get; set; }
|
public bool HasCollections { get; set; }
|
||||||
public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
|
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
|
||||||
(!Collections?.Any() ?? true);
|
&& NoFolderCiphers.Count < NoFolderListSize
|
||||||
|
&& (Collections is null || !Collections.Any());
|
||||||
public List<CipherView> Ciphers { get; set; }
|
public List<CipherView> Ciphers { get; set; }
|
||||||
public List<CipherView> FavoriteCiphers { get; set; }
|
public List<CipherView> FavoriteCiphers { get; set; }
|
||||||
public List<CipherView> NoFolderCiphers { get; set; }
|
public List<CipherView> NoFolderCiphers { get; set; }
|
||||||
@@ -97,6 +101,11 @@ namespace Bit.App.Pages
|
|||||||
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||||
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { 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
|
public bool Refreshing
|
||||||
{
|
{
|
||||||
get => _refreshing;
|
get => _refreshing;
|
||||||
@@ -172,6 +181,12 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await InitVaultFilterAsync(MainPage);
|
||||||
|
if (MainPage)
|
||||||
|
{
|
||||||
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
|
||||||
_doingLoad = true;
|
_doingLoad = true;
|
||||||
LoadedOnce = true;
|
LoadedOnce = true;
|
||||||
ShowNoData = false;
|
ShowNoData = false;
|
||||||
@@ -185,9 +200,9 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
|
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false))
|
||||||
{
|
{
|
||||||
// Remove "No Folder" from folder listing
|
// Remove "No Folder" folder from folders group
|
||||||
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +277,7 @@ namespace Bit.App.Pages
|
|||||||
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
||||||
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
||||||
}
|
}
|
||||||
if (ShowNoFolderCiphers)
|
if (ShowNoFolderCipherGroup)
|
||||||
{
|
{
|
||||||
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
||||||
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
||||||
@@ -354,6 +369,11 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async Task OnVaultFilterSelectedAsync()
|
||||||
|
{
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
@@ -380,25 +400,26 @@ namespace Bit.App.Pages
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var page = new GroupingsPage(false, type, null, null, title);
|
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectFolderAsync(FolderView folder)
|
public async Task SelectFolderAsync(FolderView folder)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
|
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
|
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectTrashAsync()
|
public async Task SelectTrashAsync()
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
|
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null,
|
||||||
|
true);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +458,7 @@ namespace Bit.App.Pages
|
|||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await _cipherService.GetAllDecryptedAsync();
|
_allCiphers = await GetAllCiphersAsync();
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -451,12 +472,11 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
Folders = await _folderService.GetAllDecryptedAsync();
|
await FillFoldersAndCollectionsAsync();
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync();
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
Collections = await _collectionService.GetAllDecryptedAsync();
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
|
HasCollections = NestedCollections?.Any() ?? false;
|
||||||
HasCollections = NestedCollections.Any();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -576,6 +596,34 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FillFoldersAndCollectionsAsync()
|
||||||
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = null;
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Folders = decFolders;
|
||||||
|
Collections = decCollections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
|
{
|
||||||
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
return folders.Any() ? folders : null;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Password History Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:PasswordHistoryView">
|
<DataTemplate x:DataType="views:PasswordHistoryView">
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -41,15 +41,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
var cipher = await _cipherService.GetAsync(CipherId);
|
var cipher = await _cipherService.GetAsync(CipherId);
|
||||||
var decCipher = await cipher.DecryptAsync();
|
var decCipher = await cipher.DecryptAsync();
|
||||||
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
ShowNoData = History.Count == 0;
|
{
|
||||||
|
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
||||||
|
ShowNoData = History.Count == 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CopyAsync(PasswordHistoryView ph)
|
private async void CopyAsync(PasswordHistoryView ph)
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(ph.Password);
|
await _clipboardService.CopyTextAsync(ph.Password);
|
||||||
_platformUtilsService.ShowToast("info", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ namespace Bit.App.Pages
|
|||||||
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
||||||
|
|
||||||
var autofocusCts = _autofocusCts;
|
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 () =>
|
_continuousAutofocusTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -52,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
while (!autofocusCts.IsCancellationRequested)
|
while (!autofocusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
|
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
await Device.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
if (!autofocusCts.IsCancellationRequested)
|
if (!autofocusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -73,7 +75,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_autofocusCts?.Cancel();
|
_autofocusCts?.Cancel();
|
||||||
|
|
||||||
await _continuousAutofocusTask;
|
if (_continuousAutofocusTask != null)
|
||||||
|
{
|
||||||
|
await _continuousAutofocusTask;
|
||||||
|
}
|
||||||
_zxing.IsScanning = false;
|
_zxing.IsScanning = false;
|
||||||
|
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool ShowCardNumber
|
public bool ShowCardNumber
|
||||||
@@ -213,6 +214,7 @@ namespace Bit.App.Pages
|
|||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string TotpCodeFormatted
|
public string TotpCodeFormatted
|
||||||
{
|
{
|
||||||
get => _totpCodeFormatted;
|
get => _totpCodeFormatted;
|
||||||
@@ -661,7 +663,7 @@ namespace Bit.App.Pages
|
|||||||
await _clipboardService.CopyTextAsync(text);
|
await _clipboardService.CopyTextAsync(text);
|
||||||
if (!string.IsNullOrWhiteSpace(name))
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
|
_platformUtilsService.ShowToastForCopiedValue(name);
|
||||||
}
|
}
|
||||||
if (id == "LoginPassword")
|
if (id == "LoginPassword")
|
||||||
{
|
{
|
||||||
@@ -754,12 +756,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (IsBooleanType)
|
if (IsBooleanType)
|
||||||
{
|
{
|
||||||
return _field.Value == "true" ? "" : "";
|
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
|
||||||
}
|
}
|
||||||
else if (IsLinkedType)
|
else if (IsLinkedType)
|
||||||
{
|
{
|
||||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||||
return " " + _i18nService.T(i18nKey);
|
return BitwardenIcons.Link + _i18nService.T(i18nKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
9541
src/App/Resources/AppResources.Designer.cs
generated
9541
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -1861,6 +1861,9 @@
|
|||||||
<value>A friendly name to describe this Send.</value>
|
<value>A friendly name to describe this Send.</value>
|
||||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Text" xml:space="preserve">
|
||||||
|
<value>Text</value>
|
||||||
|
</data>
|
||||||
<data name="TypeText" xml:space="preserve">
|
<data name="TypeText" xml:space="preserve">
|
||||||
<value>Text</value>
|
<value>Text</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1877,6 +1880,18 @@
|
|||||||
<data name="TypeFileInfo" xml:space="preserve">
|
<data name="TypeFileInfo" xml:space="preserve">
|
||||||
<value>The file you want to send.</value>
|
<value>The file you want to send.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileTypeIsSelected" xml:space="preserve">
|
||||||
|
<value>File type is selected.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileTypeIsNotSelected" xml:space="preserve">
|
||||||
|
<value>File type is not selected, tap to select.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextTypeIsSelected" xml:space="preserve">
|
||||||
|
<value>Text type is selected.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextTypeIsNotSelected" xml:space="preserve">
|
||||||
|
<value>Text type is not selected, tap to select.</value>
|
||||||
|
</data>
|
||||||
<data name="DeletionDate" xml:space="preserve">
|
<data name="DeletionDate" xml:space="preserve">
|
||||||
<value>Deletion Date</value>
|
<value>Deletion Date</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2181,4 +2196,52 @@
|
|||||||
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
||||||
<value>Enter the verification code that was sent to your email</value>
|
<value>Enter the verification code that was sent to your email</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SubmitCrashLogs" xml:space="preserve">
|
||||||
|
<value>Submit crash logs</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubmitCrashLogsDescription" xml:space="preserve">
|
||||||
|
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionsExpanded" xml:space="preserve">
|
||||||
|
<value>Options are expanded, tap to collapse.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionsCollapsed" xml:space="preserve">
|
||||||
|
<value>Options are collapsed, tap to expand.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UppercaseAtoZ" xml:space="preserve">
|
||||||
|
<value>Uppercase (A to Z)</value>
|
||||||
|
</data>
|
||||||
|
<data name="LowercaseAtoZ" xml:space="preserve">
|
||||||
|
<value>Lowercase (A to Z)</value>
|
||||||
|
</data>
|
||||||
|
<data name="NumbersZeroToNine" xml:space="preserve">
|
||||||
|
<value>Numbers (0 to 9)</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpecialCharacters" xml:space="preserve">
|
||||||
|
<value>Special Characters (!@#$%^&*)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TapToGoBack" xml:space="preserve">
|
||||||
|
<value>Tap to go back</value>
|
||||||
|
</data>
|
||||||
|
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
|
||||||
|
<value>Password is visible, tap to hide.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
|
||||||
|
<value>Password is not visible, tap to show.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterByVault" xml:space="preserve">
|
||||||
|
<value>Filter items by vault</value>
|
||||||
|
</data>
|
||||||
|
<data name="AllVaults" xml:space="preserve">
|
||||||
|
<value>All Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="Vaults" xml:space="preserve">
|
||||||
|
<value>Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="VaultFilterDescription" xml:space="preserve">
|
||||||
|
<value>Vault: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="All" xml:space="preserve">
|
||||||
|
<value>All</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Bit.App.Services
|
|||||||
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
||||||
|
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
|
||||||
@@ -28,10 +29,12 @@ namespace Bit.App.Services
|
|||||||
|
|
||||||
public MobilePlatformUtilsService(
|
public MobilePlatformUtilsService(
|
||||||
IDeviceActionService deviceActionService,
|
IDeviceActionService deviceActionService,
|
||||||
|
IClipboardService clipboardService,
|
||||||
IMessagingService messagingService,
|
IMessagingService messagingService,
|
||||||
IBroadcasterService broadcasterService)
|
IBroadcasterService broadcasterService)
|
||||||
{
|
{
|
||||||
_deviceActionService = deviceActionService;
|
_deviceActionService = deviceActionService;
|
||||||
|
_clipboardService = clipboardService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
}
|
}
|
||||||
@@ -129,6 +132,15 @@ namespace Bit.App.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ShowToastForCopiedValue(string valueNameCopied)
|
||||||
|
{
|
||||||
|
if (!_clipboardService.IsCopyNotificationHandledByPlatform())
|
||||||
|
{
|
||||||
|
ShowToast("info", null,
|
||||||
|
string.Format(AppResources.ValueHasBeenCopied, valueNameCopied));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool SupportsFido2()
|
public bool SupportsFido2()
|
||||||
{
|
{
|
||||||
return _deviceActionService.SupportsFido2();
|
return _deviceActionService.SupportsFido2();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace Bit.App.Services
|
|||||||
Constants.LastBuildKey,
|
Constants.LastBuildKey,
|
||||||
Constants.ClearCiphersCacheKey,
|
Constants.ClearCiphersCacheKey,
|
||||||
Constants.BiometricIntegrityKey,
|
Constants.BiometricIntegrityKey,
|
||||||
|
Constants.iOSExtensionActiveUserIdKey,
|
||||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||||
Constants.iOSExtensionClearCiphersCacheKey,
|
Constants.iOSExtensionClearCiphersCacheKey,
|
||||||
@@ -32,7 +33,7 @@ namespace Bit.App.Services
|
|||||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||||
Constants.RememberedEmailKey,
|
Constants.RememberedEmailKey,
|
||||||
Constants.RememberedOrgIdentifierKey,
|
Constants.RememberedOrgIdentifierKey
|
||||||
};
|
};
|
||||||
|
|
||||||
public MobileStorageService(
|
public MobileStorageService(
|
||||||
|
|||||||
@@ -268,6 +268,16 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonColor}" />
|
Value="{DynamicResource ButtonColor}" />
|
||||||
</Style>
|
</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"
|
<Style TargetType="Button"
|
||||||
ApplyToDerivedTypes="True"
|
ApplyToDerivedTypes="True"
|
||||||
Class="segmented-button">
|
Class="segmented-button">
|
||||||
|
|||||||
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public class LockNavigationParams : INavigationParams
|
||||||
|
{
|
||||||
|
public LockNavigationParams(bool autoPromptBiometric = true)
|
||||||
|
{
|
||||||
|
AutoPromptBiometric = autoPromptBiometric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutoPromptBiometric { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public class LoginNavigationParams : INavigationParams
|
||||||
|
{
|
||||||
|
public LoginNavigationParams(string email)
|
||||||
|
{
|
||||||
|
Email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Email { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,16 +97,14 @@ namespace Bit.App.Utilities
|
|||||||
else if (selection == AppResources.CopyUsername)
|
else if (selection == AppResources.CopyUsername)
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
|
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopyPassword)
|
else if (selection == AppResources.CopyPassword)
|
||||||
{
|
{
|
||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,8 +117,7 @@ namespace Bit.App.Utilities
|
|||||||
if (!string.IsNullOrWhiteSpace(totp))
|
if (!string.IsNullOrWhiteSpace(totp))
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(totp);
|
await clipboardService.CopyTextAsync(totp);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +130,7 @@ namespace Bit.App.Utilities
|
|||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopySecurityCode)
|
else if (selection == AppResources.CopySecurityCode)
|
||||||
@@ -142,16 +138,14 @@ namespace Bit.App.Utilities
|
|||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
|
|
||||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopyNotes)
|
else if (selection == AppResources.CopyNotes)
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Notes);
|
await clipboardService.CopyTextAsync(cipher.Notes);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
|
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
@@ -262,8 +256,7 @@ namespace Bit.App.Utilities
|
|||||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
await clipboardService.CopyTextAsync(GetSendUrl(send));
|
await clipboardService.CopyTextAsync(GetSendUrl(send));
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.SendLink);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ShareSendUrlAsync(SendView send)
|
public static async Task ShareSendUrlAsync(SendView send)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<object> PreValidateSso(string identifier);
|
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ namespace Bit.Core.Abstractions
|
|||||||
/// Copies the <paramref name="text"/> to the Clipboard.
|
/// 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 <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 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>
|
/// </summary>
|
||||||
/// <param name="text">Text to be copied to the Clipboard</param>
|
/// <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="expiresInMs">Expiration time in milliseconds of the copied text</param>
|
||||||
Task CopyTextAsync(string text, int expiresInMs = -1);
|
/// <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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
||||||
Task<List<Folder>> GetAllAsync();
|
Task<List<Folder>> GetAllAsync();
|
||||||
Task<List<FolderView>> GetAllDecryptedAsync();
|
Task<List<FolderView>> GetAllDecryptedAsync();
|
||||||
Task<List<TreeNode<FolderView>>> GetAllNestedAsync();
|
Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null);
|
||||||
Task<Folder> GetAsync(string id);
|
Task<Folder> GetAsync(string id);
|
||||||
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
||||||
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
public interface ILogger
|
public interface ILogger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Place necessary code to initiaze logger
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task InitAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the current logger is enable or disable.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> IsEnabled();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the state of the current logger. Setting state enabled to false will block logging.
|
||||||
|
/// </summary>
|
||||||
|
Task SetEnabled(bool value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
|
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
|
||||||
/// and looked into.
|
/// and looked into.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator);
|
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 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 SupportsFido2();
|
||||||
bool SupportsDuo();
|
bool SupportsDuo();
|
||||||
Task<bool> SupportsBiometricAsync();
|
Task<bool> SupportsBiometricAsync();
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ namespace Bit.Core.Abstractions
|
|||||||
string orgId);
|
string orgId);
|
||||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||||
int? GetPolicyInt(Policy policy, string key);
|
int? GetPolicyInt(Policy policy, string key);
|
||||||
|
Task<bool> ShouldShowVaultFilterAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
|
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||||
Task<string> GetUserIdAsync(string email);
|
Task<string> GetUserIdAsync(string email);
|
||||||
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
||||||
@@ -145,5 +146,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||||
|
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,5 +111,6 @@
|
|||||||
public const string EyeSlash = "\xe96d";
|
public const string EyeSlash = "\xe96d";
|
||||||
public const string File = "\xe96e";
|
public const string File = "\xe96e";
|
||||||
public const string Paste = "\xe96f";
|
public const string Paste = "\xe96f";
|
||||||
|
public const string ViewCellMenu = "\xe5d3";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||||
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||||
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||||
|
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
||||||
public static string EventCollectionKey = "eventCollection";
|
public static string EventCollectionKey = "eventCollection";
|
||||||
public static string RememberedEmailKey = "rememberedEmail";
|
public static string RememberedEmailKey = "rememberedEmail";
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
|
|||||||
13
src/Core/Enums/NavigationTarget.cs
Normal file
13
src/Core/Enums/NavigationTarget.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum NavigationTarget
|
||||||
|
{
|
||||||
|
HomeLogin,
|
||||||
|
Login,
|
||||||
|
Lock,
|
||||||
|
Home,
|
||||||
|
AddEditCipher,
|
||||||
|
AutofillCiphers,
|
||||||
|
SendAddEdit
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
if (defaultOptions)
|
if (defaultOptions)
|
||||||
{
|
{
|
||||||
Length = 14;
|
Length = 14;
|
||||||
Ambiguous = false;
|
AllowAmbiguousChar = true;
|
||||||
Number = true;
|
Number = true;
|
||||||
MinNumber = 1;
|
MinNumber = 1;
|
||||||
Uppercase = true;
|
Uppercase = true;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int? Length { get; set; }
|
public int? Length { get; set; }
|
||||||
public bool? Ambiguous { get; set; }
|
public bool? AllowAmbiguousChar { get; set; }
|
||||||
public bool? Number { get; set; }
|
public bool? Number { get; set; }
|
||||||
public int? MinNumber { get; set; }
|
public int? MinNumber { get; set; }
|
||||||
public bool? Uppercase { get; set; }
|
public bool? Uppercase { get; set; }
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
public void Merge(PasswordGenerationOptions defaults)
|
public void Merge(PasswordGenerationOptions defaults)
|
||||||
{
|
{
|
||||||
Length = Length ?? defaults.Length;
|
Length = Length ?? defaults.Length;
|
||||||
Ambiguous = Ambiguous ?? defaults.Ambiguous;
|
AllowAmbiguousChar = AllowAmbiguousChar ?? defaults.AllowAmbiguousChar;
|
||||||
Number = Number ?? defaults.Number;
|
Number = Number ?? defaults.Number;
|
||||||
MinNumber = MinNumber ?? defaults.MinNumber;
|
MinNumber = MinNumber ?? defaults.MinNumber;
|
||||||
Uppercase = Uppercase ?? defaults.Uppercase;
|
Uppercase = Uppercase ?? defaults.Uppercase;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class OrganizationUserResetPasswordEnrollmentRequest
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
public string ResetPasswordKey { get; set; }
|
public string ResetPasswordKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class SsoPrevalidateResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -547,7 +547,7 @@ namespace Bit.Core.Services
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> PreValidateSso(string identifier)
|
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
||||||
{
|
{
|
||||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
@@ -571,7 +571,8 @@ namespace Bit.Core.Services
|
|||||||
var error = await HandleErrorAsync(response, false, true);
|
var error = await HandleErrorAsync(response, false, true);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
return null;
|
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ namespace Bit.Core.Services
|
|||||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||||
|
await _tokenService.ClearTwoFactorTokenAsync(email);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,9 +107,12 @@ namespace Bit.Core.Services
|
|||||||
return _decryptedFolderCache;
|
return _decryptedFolderCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
|
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null)
|
||||||
{
|
{
|
||||||
var folders = await GetAllDecryptedAsync();
|
if (folders == null)
|
||||||
|
{
|
||||||
|
folders = await GetAllDecryptedAsync();
|
||||||
|
}
|
||||||
var nodes = new List<TreeNode<FolderView>>();
|
var nodes = new List<TreeNode<FolderView>>();
|
||||||
foreach (var f in folders)
|
foreach (var f in folders)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
@@ -45,6 +46,12 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Exception(Exception ex) => Debug.WriteLine(ex);
|
public void Exception(Exception ex) => Debug.WriteLine(ex);
|
||||||
|
|
||||||
|
public Task InitAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<bool> IsEnabled() => Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,13 +5,24 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Microsoft.AppCenter;
|
||||||
using Microsoft.AppCenter.Crashes;
|
using Microsoft.AppCenter.Crashes;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class Logger : ILogger
|
public class Logger : ILogger
|
||||||
{
|
{
|
||||||
|
private const string iOSAppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
|
||||||
|
private const string DroidAppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
||||||
|
|
||||||
|
private string _userId;
|
||||||
|
private string _appId;
|
||||||
|
private bool _isInitialised = false;
|
||||||
|
|
||||||
static ILogger _instance;
|
static ILogger _instance;
|
||||||
public static ILogger Instance
|
public static ILogger Instance
|
||||||
{
|
{
|
||||||
@@ -29,6 +40,60 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(new
|
||||||
|
{
|
||||||
|
AppId = _appId,
|
||||||
|
UserId = _userId
|
||||||
|
}, Formatting.Indented);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
if (_isInitialised)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var device = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService").GetDevice();
|
||||||
|
_userId = await ServiceContainer.Resolve<IStateService>("stateService").GetActiveUserIdAsync();
|
||||||
|
_appId = await ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync();
|
||||||
|
|
||||||
|
switch (device)
|
||||||
|
{
|
||||||
|
case Enums.DeviceType.Android:
|
||||||
|
AppCenter.Start(DroidAppSecret, typeof(Crashes));
|
||||||
|
break;
|
||||||
|
case Enums.DeviceType.iOS:
|
||||||
|
AppCenter.Start(iOSAppSecret, typeof(Crashes));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AppCenterException("Cannot start AppCenter. Device type is not configured.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCenter.SetUserId(_userId);
|
||||||
|
|
||||||
|
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||||
|
{
|
||||||
|
return new ErrorAttachmentLog[]
|
||||||
|
{
|
||||||
|
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_isInitialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsEnabled() => await AppCenter.IsEnabledAsync();
|
||||||
|
|
||||||
|
public async Task SetEnabled(bool value) => await AppCenter.SetEnabledAsync(value);
|
||||||
|
|
||||||
public void Error(string message,
|
public void Error(string message,
|
||||||
IDictionary<string, string> extraData = null,
|
IDictionary<string, string> extraData = null,
|
||||||
[CallerMemberName] string memberName = "",
|
[CallerMemberName] string memberName = "",
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
#if !FDROID
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -25,8 +22,9 @@ namespace Bit.Core.Services
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
||||||
// we need to track the error as well
|
// we need to track the error as well
|
||||||
Crashes.TrackError(ex);
|
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
@@ -17,5 +18,11 @@ namespace Bit.Core.Services
|
|||||||
public void Exception(Exception ex)
|
public void Exception(Exception ex)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task InitAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<bool> IsEnabled() => Task.FromResult(false);
|
||||||
|
|
||||||
|
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Bit.Core.Services
|
|||||||
// Build out other character sets
|
// Build out other character sets
|
||||||
var allCharSet = string.Empty;
|
var allCharSet = string.Empty;
|
||||||
var lowercaseCharSet = LowercaseCharSet;
|
var lowercaseCharSet = LowercaseCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var uppercaseCharSet = UppercaseCharSet;
|
var uppercaseCharSet = UppercaseCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var numberCharSet = NumberCharSet;
|
var numberCharSet = NumberCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
numberCharSet = string.Concat(numberCharSet, "01");
|
numberCharSet = string.Concat(numberCharSet, "01");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ namespace Bit.Core.Services
|
|||||||
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter, string userId = null)
|
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null,
|
||||||
|
string userId = null)
|
||||||
{
|
{
|
||||||
var policies = await GetAll(policyType, userId);
|
var policies = await GetAll(policyType, userId);
|
||||||
if (policies == null)
|
if (policies == null)
|
||||||
@@ -246,6 +247,18 @@ namespace Bit.Core.Services
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private bool? GetPolicyBool(Policy policy, string key)
|
private bool? GetPolicyBool(Policy policy, string key)
|
||||||
{
|
{
|
||||||
if (policy.Data.ContainsKey(key))
|
if (policy.Data.ContainsKey(key))
|
||||||
|
|||||||
@@ -74,28 +74,34 @@ namespace Bit.Core.Services
|
|||||||
CancellationToken ct = default, bool deleted = false)
|
CancellationToken ct = default, bool deleted = false)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
var matchedCiphers = new List<CipherView>();
|
||||||
|
var lowPriorityMatchedCiphers = new List<CipherView>();
|
||||||
query = query.Trim().ToLower();
|
query = query.Trim().ToLower();
|
||||||
return ciphers.Where(c =>
|
|
||||||
|
foreach (var c in ciphers)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
if (c.Name?.ToLower().Contains(query) ?? false)
|
if (c.Name?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
matchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (query.Length >= 8 && c.Id.StartsWith(query))
|
else if (query.Length >= 8 && c.Id.StartsWith(query))
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
else if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
else if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}).ToList();
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
matchedCiphers.AddRange(lowPriorityMatchedCiphers);
|
||||||
|
return matchedCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
|
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
|
||||||
@@ -133,25 +139,31 @@ namespace Bit.Core.Services
|
|||||||
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
|
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
|
||||||
bool deleted = false)
|
bool deleted = false)
|
||||||
{
|
{
|
||||||
|
var matchedSends = new List<SendView>();
|
||||||
|
var lowPriorityMatchSends = new List<SendView>();
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
query = query.Trim().ToLower();
|
query = query.Trim().ToLower();
|
||||||
return sends.Where(s =>
|
|
||||||
|
foreach (var s in sends)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
if (s.Name?.ToLower().Contains(query) ?? false)
|
if (s.Name?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
matchedSends.Add(s);
|
||||||
}
|
}
|
||||||
if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
else if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchSends.Add(s);
|
||||||
}
|
}
|
||||||
if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
else if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchSends.Add(s);
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}).ToList();
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
matchedSends.AddRange(lowPriorityMatchSends);
|
||||||
|
return matchedSends;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,20 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
|
private readonly IMessagingService _messagingService;
|
||||||
|
|
||||||
private State _state;
|
private State _state;
|
||||||
private bool _migrationChecked;
|
private bool _migrationChecked;
|
||||||
|
|
||||||
public List<AccountView> AccountViews { get; set; }
|
public List<AccountView> AccountViews { get; set; }
|
||||||
|
|
||||||
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
public StateService(IStorageService storageService,
|
||||||
|
IStorageService secureStorageService,
|
||||||
|
IMessagingService messagingService)
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_secureStorageService = secureStorageService;
|
_secureStorageService = secureStorageService;
|
||||||
|
_messagingService = messagingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetActiveUserIdAsync()
|
public async Task<string> GetActiveUserIdAsync()
|
||||||
@@ -67,6 +71,28 @@ namespace Bit.Core.Services
|
|||||||
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
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)
|
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return await GetAccessTokenAsync(userId) != null;
|
return await GetAccessTokenAsync(userId) != null;
|
||||||
@@ -1372,10 +1398,6 @@ namespace Bit.Core.Services
|
|||||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||||
await SetEncryptedSendsAsync(null, userId);
|
await SetEncryptedSendsAsync(null, userId);
|
||||||
await SetSettingsAsync(null, userId);
|
await SetSettingsAsync(null, userId);
|
||||||
if (!string.IsNullOrWhiteSpace(email))
|
|
||||||
{
|
|
||||||
await SetTwoFactorTokenAsync(null, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInitiated)
|
if (userInitiated)
|
||||||
{
|
{
|
||||||
@@ -1514,6 +1536,16 @@ namespace Bit.Core.Services
|
|||||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
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()
|
private async Task CheckStateAsync()
|
||||||
{
|
{
|
||||||
if (!_migrationChecked)
|
if (!_migrationChecked)
|
||||||
|
|||||||
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user